@usethrottle/cart 0.1.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.
- package/README.md +118 -0
- package/dist/index.cjs +130 -0
- package/dist/index.d.cts +214 -0
- package/dist/index.d.ts +214 -0
- package/dist/index.js +102 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# @usethrottle/cart
|
|
2
|
+
|
|
3
|
+
Typed Node.js REST client for the Throttle Cart API.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @usethrottle/cart
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
End-to-end: create a cart, build it out, and convert it to an order.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { CartClient } from '@usethrottle/cart';
|
|
17
|
+
|
|
18
|
+
const client = new CartClient({ apiKey: process.env.THROTTLE_API_KEY! });
|
|
19
|
+
|
|
20
|
+
// 1. Create a cart
|
|
21
|
+
const cart = await client.carts.create({ storeId: 'store_abc', currency: 'USD' });
|
|
22
|
+
|
|
23
|
+
// 2. Add a line item
|
|
24
|
+
await client.items.add(cart.id, {
|
|
25
|
+
type: 'product',
|
|
26
|
+
name: 'Premium Widget',
|
|
27
|
+
unitPrice: 2999,
|
|
28
|
+
quantity: 2,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// 3. Select a shipping method
|
|
32
|
+
await client.shipping.select(cart.id, {
|
|
33
|
+
methodId: 'fedex_ground',
|
|
34
|
+
displayName: 'FedEx Ground',
|
|
35
|
+
rateAmount: 599,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// 4. Apply a discount code
|
|
39
|
+
await client.discounts.apply(cart.id, 'SAVE10');
|
|
40
|
+
|
|
41
|
+
// 5. Set tax lines
|
|
42
|
+
await client.taxLines.set(cart.id, [
|
|
43
|
+
{ lineItemId: '...', jurisdictionCode: 'US-NY', taxType: 'sales', rate: 0.04, amount: 400 },
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
// 6. Checkout → Order
|
|
47
|
+
const order = await client.carts.checkout(cart.id, { paymentMethod: 'card' });
|
|
48
|
+
console.log(order.id, order.status);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
All monetary values are integers in the smallest currency unit (cents for USD).
|
|
52
|
+
|
|
53
|
+
## Subscriptions
|
|
54
|
+
|
|
55
|
+
Pass `type: 'subscription'` and a `recurring` block on the line item:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
await client.items.add(cart.id, {
|
|
59
|
+
type: 'subscription',
|
|
60
|
+
name: 'Pro Plan',
|
|
61
|
+
unitPrice: 1900,
|
|
62
|
+
quantity: 1,
|
|
63
|
+
recurring: { interval: 'month', count: null }, // count: null = until cancelled
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Then call `client.carts.checkout` as normal. Set `count` to a number to cap
|
|
68
|
+
the billing cycles.
|
|
69
|
+
|
|
70
|
+
## Events
|
|
71
|
+
|
|
72
|
+
Every mutation appends an immutable event to the cart's event log. Fetch it with:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const events = await client.events.list(cart.id);
|
|
76
|
+
// [{ eventType: 'cart.created', sequence: 1, ... }, ...]
|
|
77
|
+
|
|
78
|
+
// Poll only new events since the last sequence you saw
|
|
79
|
+
const newEvents = await client.events.list(cart.id, { sinceSequence: 5, limit: 50 });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For real-time delivery, subscribe to Throttle's outbound webhooks from your
|
|
83
|
+
merchant dashboard. Webhooks are signed with HMAC-SHA256 and fire from
|
|
84
|
+
Throttle's servers to your backend over HTTPS.
|
|
85
|
+
|
|
86
|
+
## Errors
|
|
87
|
+
|
|
88
|
+
All non-2xx responses throw `ThrottleApiError`. Inspect `code` for the
|
|
89
|
+
machine-readable reason and `statusCode` for the HTTP status:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { ThrottleApiError } from '@usethrottle/cart';
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
await client.discounts.apply(cart.id, 'EXPIRED');
|
|
96
|
+
} catch (e) {
|
|
97
|
+
if (e instanceof ThrottleApiError && e.code === 'discount_invalid') {
|
|
98
|
+
// surface a user-friendly message
|
|
99
|
+
}
|
|
100
|
+
if (e instanceof ThrottleApiError) {
|
|
101
|
+
console.error(e.statusCode, e.code, e.message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Client options
|
|
107
|
+
|
|
108
|
+
| Option | Default | Description |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `apiKey` | — | **Required.** Your Throttle secret key (`sk_…`). |
|
|
111
|
+
| `baseUrl` | `https://throttle-api-gff1.onrender.com` | Override for local dev or staging. |
|
|
112
|
+
| `timeoutMs` | `30000` | Per-request abort timeout in ms. |
|
|
113
|
+
| `fetch` | `globalThis.fetch` | Custom fetch implementation (e.g. `node-fetch`). |
|
|
114
|
+
|
|
115
|
+
## See also
|
|
116
|
+
|
|
117
|
+
- [API client (auto-generated): `@usethrottle/api-client`](https://www.npmjs.com/package/@usethrottle/api-client)
|
|
118
|
+
- [React embed components: `@usethrottle/checkout-react`](https://www.npmjs.com/package/@usethrottle/checkout-react)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
CartClient: () => CartClient,
|
|
24
|
+
ThrottleApiError: () => ThrottleApiError
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/errors.ts
|
|
29
|
+
var ThrottleApiError = class extends Error {
|
|
30
|
+
code;
|
|
31
|
+
statusCode;
|
|
32
|
+
details;
|
|
33
|
+
constructor(args) {
|
|
34
|
+
super(args.message);
|
|
35
|
+
this.name = "ThrottleApiError";
|
|
36
|
+
this.code = args.code;
|
|
37
|
+
this.statusCode = args.statusCode;
|
|
38
|
+
this.details = args.details;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/client.ts
|
|
43
|
+
var CartClient = class {
|
|
44
|
+
apiKey;
|
|
45
|
+
baseUrl;
|
|
46
|
+
fetchImpl;
|
|
47
|
+
timeoutMs;
|
|
48
|
+
constructor(opts) {
|
|
49
|
+
if (!opts.apiKey) throw new Error("CartClient: apiKey is required");
|
|
50
|
+
this.apiKey = opts.apiKey;
|
|
51
|
+
this.baseUrl = (opts.baseUrl ?? "https://throttle-api-gff1.onrender.com").replace(/\/+$/, "");
|
|
52
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
53
|
+
this.timeoutMs = opts.timeoutMs ?? 3e4;
|
|
54
|
+
}
|
|
55
|
+
async request(method, path, body) {
|
|
56
|
+
const url = path.startsWith("http") ? path : `${this.baseUrl}${path.startsWith("/") ? path : "/" + path}`;
|
|
57
|
+
const ctrl = new AbortController();
|
|
58
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
59
|
+
try {
|
|
60
|
+
const res = await this.fetchImpl(url, {
|
|
61
|
+
method,
|
|
62
|
+
headers: { "x-api-key": this.apiKey, "content-type": "application/json" },
|
|
63
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
64
|
+
signal: ctrl.signal
|
|
65
|
+
});
|
|
66
|
+
const json = await res.json().catch(() => ({}));
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
throw new ThrottleApiError({
|
|
69
|
+
code: json?.error?.code ?? "unknown_error",
|
|
70
|
+
message: json?.error?.message ?? `HTTP ${res.status}`,
|
|
71
|
+
statusCode: res.status,
|
|
72
|
+
details: json?.error?.details
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return json.data;
|
|
76
|
+
} finally {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
get carts() {
|
|
81
|
+
return {
|
|
82
|
+
create: (input) => this.request("POST", "/api/v1/carts", input),
|
|
83
|
+
get: (id) => this.request("GET", `/api/v1/carts/${id}`),
|
|
84
|
+
update: (id, input) => this.request("PATCH", `/api/v1/carts/${id}`, input),
|
|
85
|
+
checkout: (id, input) => this.request("POST", `/api/v1/carts/${id}/checkout`, input ?? {}),
|
|
86
|
+
merge: (id, customerId) => this.request("POST", `/api/v1/carts/${id}/merge`, { customerId })
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
get items() {
|
|
90
|
+
return {
|
|
91
|
+
add: (cartId, input) => this.request("POST", `/api/v1/carts/${cartId}/items`, input),
|
|
92
|
+
update: (cartId, itemId, input) => this.request("PATCH", `/api/v1/carts/${cartId}/items/${itemId}`, input),
|
|
93
|
+
remove: (cartId, itemId) => this.request("DELETE", `/api/v1/carts/${cartId}/items/${itemId}`)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
get shipping() {
|
|
97
|
+
return {
|
|
98
|
+
select: (cartId, input) => this.request("POST", `/api/v1/carts/${cartId}/shipping`, input),
|
|
99
|
+
clear: (cartId) => this.request("DELETE", `/api/v1/carts/${cartId}/shipping`)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
get discounts() {
|
|
103
|
+
return {
|
|
104
|
+
apply: (cartId, code) => this.request("POST", `/api/v1/carts/${cartId}/apply-discount`, { code }),
|
|
105
|
+
remove: (cartId) => this.request("DELETE", `/api/v1/carts/${cartId}/discount`)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
get taxLines() {
|
|
109
|
+
return {
|
|
110
|
+
set: (cartId, lines) => this.request("PUT", `/api/v1/carts/${cartId}/tax-lines`, { lines }),
|
|
111
|
+
clear: (cartId) => this.request("DELETE", `/api/v1/carts/${cartId}/tax-lines`)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
get events() {
|
|
115
|
+
return {
|
|
116
|
+
list: (cartId, opts) => {
|
|
117
|
+
const params = new URLSearchParams();
|
|
118
|
+
if (opts?.sinceSequence !== void 0) params.set("sinceSequence", String(opts.sinceSequence));
|
|
119
|
+
if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
|
|
120
|
+
const qs = params.toString();
|
|
121
|
+
return this.request("GET", `/api/v1/carts/${cartId}/events${qs ? "?" + qs : ""}`);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
127
|
+
0 && (module.exports = {
|
|
128
|
+
CartClient,
|
|
129
|
+
ThrottleApiError
|
|
130
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
interface Cart {
|
|
2
|
+
id: string;
|
|
3
|
+
merchantId: string;
|
|
4
|
+
storeId: string | null;
|
|
5
|
+
customerId: string | null;
|
|
6
|
+
status: 'open' | 'checkout' | 'converted' | 'abandoned';
|
|
7
|
+
currency: string;
|
|
8
|
+
subtotal: number;
|
|
9
|
+
taxTotal: number;
|
|
10
|
+
discountTotal: number;
|
|
11
|
+
shippingTotal: number;
|
|
12
|
+
total: number;
|
|
13
|
+
billingAddress: Record<string, unknown> | null;
|
|
14
|
+
shippingAddress: Record<string, unknown> | null;
|
|
15
|
+
notes: string | null;
|
|
16
|
+
expiresAt: string | null;
|
|
17
|
+
externalRef?: {
|
|
18
|
+
connectionId: string;
|
|
19
|
+
externalCartId: string;
|
|
20
|
+
externalMetadata: Record<string, unknown>;
|
|
21
|
+
};
|
|
22
|
+
selectedShipping?: SelectedShipping | null;
|
|
23
|
+
appliedDiscount?: AppliedDiscount | null;
|
|
24
|
+
taxLines?: TaxLine[];
|
|
25
|
+
lineItems?: LineItem[];
|
|
26
|
+
createdAt: string;
|
|
27
|
+
updatedAt: string;
|
|
28
|
+
}
|
|
29
|
+
interface LineItem {
|
|
30
|
+
id: string;
|
|
31
|
+
cartId: string;
|
|
32
|
+
type: 'product' | 'subscription' | 'service' | 'ticket' | 'donation' | 'custom';
|
|
33
|
+
referenceId: string | null;
|
|
34
|
+
name: string;
|
|
35
|
+
description: string | null;
|
|
36
|
+
unitPrice: number;
|
|
37
|
+
quantity: number;
|
|
38
|
+
subtotal: number;
|
|
39
|
+
taxAmount: number;
|
|
40
|
+
discountAmount: number;
|
|
41
|
+
total: number;
|
|
42
|
+
recurring: {
|
|
43
|
+
interval: string;
|
|
44
|
+
count: number | null;
|
|
45
|
+
} | null;
|
|
46
|
+
imageUrl: string | null;
|
|
47
|
+
position: number;
|
|
48
|
+
metadata: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
interface SelectedShipping {
|
|
51
|
+
methodId: string;
|
|
52
|
+
carrier: string | null;
|
|
53
|
+
serviceCode: string | null;
|
|
54
|
+
displayName: string;
|
|
55
|
+
rateAmount: number;
|
|
56
|
+
currency: string;
|
|
57
|
+
estimatedDeliveryDays: number | null;
|
|
58
|
+
}
|
|
59
|
+
interface AppliedDiscount {
|
|
60
|
+
code: string;
|
|
61
|
+
type: 'percentage' | 'fixed_amount' | 'free_shipping';
|
|
62
|
+
amount: number;
|
|
63
|
+
currency: string;
|
|
64
|
+
freeShipping: boolean;
|
|
65
|
+
validUntil: string | null;
|
|
66
|
+
snapshotHash: string;
|
|
67
|
+
}
|
|
68
|
+
interface TaxLine {
|
|
69
|
+
id: string;
|
|
70
|
+
lineItemId: string;
|
|
71
|
+
jurisdictionCode: string;
|
|
72
|
+
jurisdictionName: string | null;
|
|
73
|
+
taxType: 'sales' | 'vat' | 'gst' | 'pst' | 'hst' | 'service' | 'excise' | 'other';
|
|
74
|
+
rate: number;
|
|
75
|
+
amount: number;
|
|
76
|
+
currency: string;
|
|
77
|
+
}
|
|
78
|
+
interface CartEvent {
|
|
79
|
+
id: string;
|
|
80
|
+
cartId: string;
|
|
81
|
+
eventType: string;
|
|
82
|
+
sequence: number;
|
|
83
|
+
payload: Record<string, unknown>;
|
|
84
|
+
actorType: 'system' | 'merchant' | 'buyer' | 'connector';
|
|
85
|
+
actorId: string | null;
|
|
86
|
+
createdAt: string;
|
|
87
|
+
}
|
|
88
|
+
interface CreateCartInput {
|
|
89
|
+
storeId: string;
|
|
90
|
+
customerId?: string;
|
|
91
|
+
currency?: string;
|
|
92
|
+
metadata?: Record<string, unknown>;
|
|
93
|
+
}
|
|
94
|
+
interface UpdateCartInput {
|
|
95
|
+
customerId?: string;
|
|
96
|
+
billingAddress?: Record<string, unknown>;
|
|
97
|
+
shippingAddress?: Record<string, unknown>;
|
|
98
|
+
notes?: string;
|
|
99
|
+
metadata?: Record<string, unknown>;
|
|
100
|
+
}
|
|
101
|
+
interface AddLineItemInput {
|
|
102
|
+
type?: LineItem['type'];
|
|
103
|
+
referenceId?: string;
|
|
104
|
+
name: string;
|
|
105
|
+
description?: string;
|
|
106
|
+
unitPrice: number;
|
|
107
|
+
quantity?: number;
|
|
108
|
+
taxAmount?: number;
|
|
109
|
+
discountAmount?: number;
|
|
110
|
+
recurring?: {
|
|
111
|
+
interval: string;
|
|
112
|
+
count: number | null;
|
|
113
|
+
} | null;
|
|
114
|
+
imageUrl?: string;
|
|
115
|
+
metadata?: Record<string, unknown>;
|
|
116
|
+
}
|
|
117
|
+
interface UpdateLineItemInput {
|
|
118
|
+
quantity?: number;
|
|
119
|
+
unitPrice?: number;
|
|
120
|
+
taxAmount?: number;
|
|
121
|
+
discountAmount?: number;
|
|
122
|
+
metadata?: Record<string, unknown>;
|
|
123
|
+
}
|
|
124
|
+
interface SelectShippingInput {
|
|
125
|
+
methodId: string;
|
|
126
|
+
displayName: string;
|
|
127
|
+
rateAmount: number;
|
|
128
|
+
currency?: string;
|
|
129
|
+
carrier?: string;
|
|
130
|
+
serviceCode?: string;
|
|
131
|
+
estimatedDeliveryDays?: number;
|
|
132
|
+
}
|
|
133
|
+
interface TaxLineInput {
|
|
134
|
+
lineItemId: string;
|
|
135
|
+
jurisdictionCode: string;
|
|
136
|
+
jurisdictionName?: string;
|
|
137
|
+
taxType: TaxLine['taxType'];
|
|
138
|
+
rate: number;
|
|
139
|
+
amount: number;
|
|
140
|
+
currency?: string;
|
|
141
|
+
}
|
|
142
|
+
interface CheckoutInput {
|
|
143
|
+
paymentMethod?: string;
|
|
144
|
+
netDays?: number;
|
|
145
|
+
termsAccepted?: boolean;
|
|
146
|
+
termsSnapshot?: string;
|
|
147
|
+
invoiceNumber?: string;
|
|
148
|
+
metadata?: Record<string, unknown>;
|
|
149
|
+
}
|
|
150
|
+
interface Order {
|
|
151
|
+
id: string;
|
|
152
|
+
status: string;
|
|
153
|
+
total: number;
|
|
154
|
+
currency: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
interface CartClientOptions {
|
|
158
|
+
apiKey: string;
|
|
159
|
+
baseUrl?: string;
|
|
160
|
+
fetch?: typeof globalThis.fetch;
|
|
161
|
+
timeoutMs?: number;
|
|
162
|
+
}
|
|
163
|
+
declare class CartClient {
|
|
164
|
+
private readonly apiKey;
|
|
165
|
+
private readonly baseUrl;
|
|
166
|
+
private readonly fetchImpl;
|
|
167
|
+
private readonly timeoutMs;
|
|
168
|
+
constructor(opts: CartClientOptions);
|
|
169
|
+
request<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
|
|
170
|
+
get carts(): {
|
|
171
|
+
create: (input: CreateCartInput) => Promise<Cart>;
|
|
172
|
+
get: (id: string) => Promise<Cart>;
|
|
173
|
+
update: (id: string, input: UpdateCartInput) => Promise<Cart>;
|
|
174
|
+
checkout: (id: string, input?: CheckoutInput) => Promise<Order>;
|
|
175
|
+
merge: (id: string, customerId: string) => Promise<Cart>;
|
|
176
|
+
};
|
|
177
|
+
get items(): {
|
|
178
|
+
add: (cartId: string, input: AddLineItemInput) => Promise<LineItem>;
|
|
179
|
+
update: (cartId: string, itemId: string, input: UpdateLineItemInput) => Promise<LineItem>;
|
|
180
|
+
remove: (cartId: string, itemId: string) => Promise<void>;
|
|
181
|
+
};
|
|
182
|
+
get shipping(): {
|
|
183
|
+
select: (cartId: string, input: SelectShippingInput) => Promise<Cart>;
|
|
184
|
+
clear: (cartId: string) => Promise<Cart>;
|
|
185
|
+
};
|
|
186
|
+
get discounts(): {
|
|
187
|
+
apply: (cartId: string, code: string) => Promise<Cart>;
|
|
188
|
+
remove: (cartId: string) => Promise<Cart>;
|
|
189
|
+
};
|
|
190
|
+
get taxLines(): {
|
|
191
|
+
set: (cartId: string, lines: TaxLineInput[]) => Promise<Cart>;
|
|
192
|
+
clear: (cartId: string) => Promise<Cart>;
|
|
193
|
+
};
|
|
194
|
+
get events(): {
|
|
195
|
+
list: (cartId: string, opts?: {
|
|
196
|
+
sinceSequence?: number;
|
|
197
|
+
limit?: number;
|
|
198
|
+
}) => Promise<CartEvent[]>;
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
declare class ThrottleApiError extends Error {
|
|
203
|
+
readonly code: string;
|
|
204
|
+
readonly statusCode: number;
|
|
205
|
+
readonly details?: Record<string, unknown>;
|
|
206
|
+
constructor(args: {
|
|
207
|
+
code: string;
|
|
208
|
+
message: string;
|
|
209
|
+
statusCode: number;
|
|
210
|
+
details?: Record<string, unknown>;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { type AddLineItemInput, type AppliedDiscount, type Cart, CartClient, type CartEvent, type CheckoutInput, type CreateCartInput, type LineItem, type Order, type SelectShippingInput, type SelectedShipping, type TaxLine, type TaxLineInput, ThrottleApiError, type UpdateCartInput, type UpdateLineItemInput };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
interface Cart {
|
|
2
|
+
id: string;
|
|
3
|
+
merchantId: string;
|
|
4
|
+
storeId: string | null;
|
|
5
|
+
customerId: string | null;
|
|
6
|
+
status: 'open' | 'checkout' | 'converted' | 'abandoned';
|
|
7
|
+
currency: string;
|
|
8
|
+
subtotal: number;
|
|
9
|
+
taxTotal: number;
|
|
10
|
+
discountTotal: number;
|
|
11
|
+
shippingTotal: number;
|
|
12
|
+
total: number;
|
|
13
|
+
billingAddress: Record<string, unknown> | null;
|
|
14
|
+
shippingAddress: Record<string, unknown> | null;
|
|
15
|
+
notes: string | null;
|
|
16
|
+
expiresAt: string | null;
|
|
17
|
+
externalRef?: {
|
|
18
|
+
connectionId: string;
|
|
19
|
+
externalCartId: string;
|
|
20
|
+
externalMetadata: Record<string, unknown>;
|
|
21
|
+
};
|
|
22
|
+
selectedShipping?: SelectedShipping | null;
|
|
23
|
+
appliedDiscount?: AppliedDiscount | null;
|
|
24
|
+
taxLines?: TaxLine[];
|
|
25
|
+
lineItems?: LineItem[];
|
|
26
|
+
createdAt: string;
|
|
27
|
+
updatedAt: string;
|
|
28
|
+
}
|
|
29
|
+
interface LineItem {
|
|
30
|
+
id: string;
|
|
31
|
+
cartId: string;
|
|
32
|
+
type: 'product' | 'subscription' | 'service' | 'ticket' | 'donation' | 'custom';
|
|
33
|
+
referenceId: string | null;
|
|
34
|
+
name: string;
|
|
35
|
+
description: string | null;
|
|
36
|
+
unitPrice: number;
|
|
37
|
+
quantity: number;
|
|
38
|
+
subtotal: number;
|
|
39
|
+
taxAmount: number;
|
|
40
|
+
discountAmount: number;
|
|
41
|
+
total: number;
|
|
42
|
+
recurring: {
|
|
43
|
+
interval: string;
|
|
44
|
+
count: number | null;
|
|
45
|
+
} | null;
|
|
46
|
+
imageUrl: string | null;
|
|
47
|
+
position: number;
|
|
48
|
+
metadata: Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
interface SelectedShipping {
|
|
51
|
+
methodId: string;
|
|
52
|
+
carrier: string | null;
|
|
53
|
+
serviceCode: string | null;
|
|
54
|
+
displayName: string;
|
|
55
|
+
rateAmount: number;
|
|
56
|
+
currency: string;
|
|
57
|
+
estimatedDeliveryDays: number | null;
|
|
58
|
+
}
|
|
59
|
+
interface AppliedDiscount {
|
|
60
|
+
code: string;
|
|
61
|
+
type: 'percentage' | 'fixed_amount' | 'free_shipping';
|
|
62
|
+
amount: number;
|
|
63
|
+
currency: string;
|
|
64
|
+
freeShipping: boolean;
|
|
65
|
+
validUntil: string | null;
|
|
66
|
+
snapshotHash: string;
|
|
67
|
+
}
|
|
68
|
+
interface TaxLine {
|
|
69
|
+
id: string;
|
|
70
|
+
lineItemId: string;
|
|
71
|
+
jurisdictionCode: string;
|
|
72
|
+
jurisdictionName: string | null;
|
|
73
|
+
taxType: 'sales' | 'vat' | 'gst' | 'pst' | 'hst' | 'service' | 'excise' | 'other';
|
|
74
|
+
rate: number;
|
|
75
|
+
amount: number;
|
|
76
|
+
currency: string;
|
|
77
|
+
}
|
|
78
|
+
interface CartEvent {
|
|
79
|
+
id: string;
|
|
80
|
+
cartId: string;
|
|
81
|
+
eventType: string;
|
|
82
|
+
sequence: number;
|
|
83
|
+
payload: Record<string, unknown>;
|
|
84
|
+
actorType: 'system' | 'merchant' | 'buyer' | 'connector';
|
|
85
|
+
actorId: string | null;
|
|
86
|
+
createdAt: string;
|
|
87
|
+
}
|
|
88
|
+
interface CreateCartInput {
|
|
89
|
+
storeId: string;
|
|
90
|
+
customerId?: string;
|
|
91
|
+
currency?: string;
|
|
92
|
+
metadata?: Record<string, unknown>;
|
|
93
|
+
}
|
|
94
|
+
interface UpdateCartInput {
|
|
95
|
+
customerId?: string;
|
|
96
|
+
billingAddress?: Record<string, unknown>;
|
|
97
|
+
shippingAddress?: Record<string, unknown>;
|
|
98
|
+
notes?: string;
|
|
99
|
+
metadata?: Record<string, unknown>;
|
|
100
|
+
}
|
|
101
|
+
interface AddLineItemInput {
|
|
102
|
+
type?: LineItem['type'];
|
|
103
|
+
referenceId?: string;
|
|
104
|
+
name: string;
|
|
105
|
+
description?: string;
|
|
106
|
+
unitPrice: number;
|
|
107
|
+
quantity?: number;
|
|
108
|
+
taxAmount?: number;
|
|
109
|
+
discountAmount?: number;
|
|
110
|
+
recurring?: {
|
|
111
|
+
interval: string;
|
|
112
|
+
count: number | null;
|
|
113
|
+
} | null;
|
|
114
|
+
imageUrl?: string;
|
|
115
|
+
metadata?: Record<string, unknown>;
|
|
116
|
+
}
|
|
117
|
+
interface UpdateLineItemInput {
|
|
118
|
+
quantity?: number;
|
|
119
|
+
unitPrice?: number;
|
|
120
|
+
taxAmount?: number;
|
|
121
|
+
discountAmount?: number;
|
|
122
|
+
metadata?: Record<string, unknown>;
|
|
123
|
+
}
|
|
124
|
+
interface SelectShippingInput {
|
|
125
|
+
methodId: string;
|
|
126
|
+
displayName: string;
|
|
127
|
+
rateAmount: number;
|
|
128
|
+
currency?: string;
|
|
129
|
+
carrier?: string;
|
|
130
|
+
serviceCode?: string;
|
|
131
|
+
estimatedDeliveryDays?: number;
|
|
132
|
+
}
|
|
133
|
+
interface TaxLineInput {
|
|
134
|
+
lineItemId: string;
|
|
135
|
+
jurisdictionCode: string;
|
|
136
|
+
jurisdictionName?: string;
|
|
137
|
+
taxType: TaxLine['taxType'];
|
|
138
|
+
rate: number;
|
|
139
|
+
amount: number;
|
|
140
|
+
currency?: string;
|
|
141
|
+
}
|
|
142
|
+
interface CheckoutInput {
|
|
143
|
+
paymentMethod?: string;
|
|
144
|
+
netDays?: number;
|
|
145
|
+
termsAccepted?: boolean;
|
|
146
|
+
termsSnapshot?: string;
|
|
147
|
+
invoiceNumber?: string;
|
|
148
|
+
metadata?: Record<string, unknown>;
|
|
149
|
+
}
|
|
150
|
+
interface Order {
|
|
151
|
+
id: string;
|
|
152
|
+
status: string;
|
|
153
|
+
total: number;
|
|
154
|
+
currency: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
interface CartClientOptions {
|
|
158
|
+
apiKey: string;
|
|
159
|
+
baseUrl?: string;
|
|
160
|
+
fetch?: typeof globalThis.fetch;
|
|
161
|
+
timeoutMs?: number;
|
|
162
|
+
}
|
|
163
|
+
declare class CartClient {
|
|
164
|
+
private readonly apiKey;
|
|
165
|
+
private readonly baseUrl;
|
|
166
|
+
private readonly fetchImpl;
|
|
167
|
+
private readonly timeoutMs;
|
|
168
|
+
constructor(opts: CartClientOptions);
|
|
169
|
+
request<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
|
|
170
|
+
get carts(): {
|
|
171
|
+
create: (input: CreateCartInput) => Promise<Cart>;
|
|
172
|
+
get: (id: string) => Promise<Cart>;
|
|
173
|
+
update: (id: string, input: UpdateCartInput) => Promise<Cart>;
|
|
174
|
+
checkout: (id: string, input?: CheckoutInput) => Promise<Order>;
|
|
175
|
+
merge: (id: string, customerId: string) => Promise<Cart>;
|
|
176
|
+
};
|
|
177
|
+
get items(): {
|
|
178
|
+
add: (cartId: string, input: AddLineItemInput) => Promise<LineItem>;
|
|
179
|
+
update: (cartId: string, itemId: string, input: UpdateLineItemInput) => Promise<LineItem>;
|
|
180
|
+
remove: (cartId: string, itemId: string) => Promise<void>;
|
|
181
|
+
};
|
|
182
|
+
get shipping(): {
|
|
183
|
+
select: (cartId: string, input: SelectShippingInput) => Promise<Cart>;
|
|
184
|
+
clear: (cartId: string) => Promise<Cart>;
|
|
185
|
+
};
|
|
186
|
+
get discounts(): {
|
|
187
|
+
apply: (cartId: string, code: string) => Promise<Cart>;
|
|
188
|
+
remove: (cartId: string) => Promise<Cart>;
|
|
189
|
+
};
|
|
190
|
+
get taxLines(): {
|
|
191
|
+
set: (cartId: string, lines: TaxLineInput[]) => Promise<Cart>;
|
|
192
|
+
clear: (cartId: string) => Promise<Cart>;
|
|
193
|
+
};
|
|
194
|
+
get events(): {
|
|
195
|
+
list: (cartId: string, opts?: {
|
|
196
|
+
sinceSequence?: number;
|
|
197
|
+
limit?: number;
|
|
198
|
+
}) => Promise<CartEvent[]>;
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
declare class ThrottleApiError extends Error {
|
|
203
|
+
readonly code: string;
|
|
204
|
+
readonly statusCode: number;
|
|
205
|
+
readonly details?: Record<string, unknown>;
|
|
206
|
+
constructor(args: {
|
|
207
|
+
code: string;
|
|
208
|
+
message: string;
|
|
209
|
+
statusCode: number;
|
|
210
|
+
details?: Record<string, unknown>;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { type AddLineItemInput, type AppliedDiscount, type Cart, CartClient, type CartEvent, type CheckoutInput, type CreateCartInput, type LineItem, type Order, type SelectShippingInput, type SelectedShipping, type TaxLine, type TaxLineInput, ThrottleApiError, type UpdateCartInput, type UpdateLineItemInput };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var ThrottleApiError = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
statusCode;
|
|
5
|
+
details;
|
|
6
|
+
constructor(args) {
|
|
7
|
+
super(args.message);
|
|
8
|
+
this.name = "ThrottleApiError";
|
|
9
|
+
this.code = args.code;
|
|
10
|
+
this.statusCode = args.statusCode;
|
|
11
|
+
this.details = args.details;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/client.ts
|
|
16
|
+
var CartClient = class {
|
|
17
|
+
apiKey;
|
|
18
|
+
baseUrl;
|
|
19
|
+
fetchImpl;
|
|
20
|
+
timeoutMs;
|
|
21
|
+
constructor(opts) {
|
|
22
|
+
if (!opts.apiKey) throw new Error("CartClient: apiKey is required");
|
|
23
|
+
this.apiKey = opts.apiKey;
|
|
24
|
+
this.baseUrl = (opts.baseUrl ?? "https://throttle-api-gff1.onrender.com").replace(/\/+$/, "");
|
|
25
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
26
|
+
this.timeoutMs = opts.timeoutMs ?? 3e4;
|
|
27
|
+
}
|
|
28
|
+
async request(method, path, body) {
|
|
29
|
+
const url = path.startsWith("http") ? path : `${this.baseUrl}${path.startsWith("/") ? path : "/" + path}`;
|
|
30
|
+
const ctrl = new AbortController();
|
|
31
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
32
|
+
try {
|
|
33
|
+
const res = await this.fetchImpl(url, {
|
|
34
|
+
method,
|
|
35
|
+
headers: { "x-api-key": this.apiKey, "content-type": "application/json" },
|
|
36
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
37
|
+
signal: ctrl.signal
|
|
38
|
+
});
|
|
39
|
+
const json = await res.json().catch(() => ({}));
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
throw new ThrottleApiError({
|
|
42
|
+
code: json?.error?.code ?? "unknown_error",
|
|
43
|
+
message: json?.error?.message ?? `HTTP ${res.status}`,
|
|
44
|
+
statusCode: res.status,
|
|
45
|
+
details: json?.error?.details
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return json.data;
|
|
49
|
+
} finally {
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
get carts() {
|
|
54
|
+
return {
|
|
55
|
+
create: (input) => this.request("POST", "/api/v1/carts", input),
|
|
56
|
+
get: (id) => this.request("GET", `/api/v1/carts/${id}`),
|
|
57
|
+
update: (id, input) => this.request("PATCH", `/api/v1/carts/${id}`, input),
|
|
58
|
+
checkout: (id, input) => this.request("POST", `/api/v1/carts/${id}/checkout`, input ?? {}),
|
|
59
|
+
merge: (id, customerId) => this.request("POST", `/api/v1/carts/${id}/merge`, { customerId })
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
get items() {
|
|
63
|
+
return {
|
|
64
|
+
add: (cartId, input) => this.request("POST", `/api/v1/carts/${cartId}/items`, input),
|
|
65
|
+
update: (cartId, itemId, input) => this.request("PATCH", `/api/v1/carts/${cartId}/items/${itemId}`, input),
|
|
66
|
+
remove: (cartId, itemId) => this.request("DELETE", `/api/v1/carts/${cartId}/items/${itemId}`)
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
get shipping() {
|
|
70
|
+
return {
|
|
71
|
+
select: (cartId, input) => this.request("POST", `/api/v1/carts/${cartId}/shipping`, input),
|
|
72
|
+
clear: (cartId) => this.request("DELETE", `/api/v1/carts/${cartId}/shipping`)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
get discounts() {
|
|
76
|
+
return {
|
|
77
|
+
apply: (cartId, code) => this.request("POST", `/api/v1/carts/${cartId}/apply-discount`, { code }),
|
|
78
|
+
remove: (cartId) => this.request("DELETE", `/api/v1/carts/${cartId}/discount`)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
get taxLines() {
|
|
82
|
+
return {
|
|
83
|
+
set: (cartId, lines) => this.request("PUT", `/api/v1/carts/${cartId}/tax-lines`, { lines }),
|
|
84
|
+
clear: (cartId) => this.request("DELETE", `/api/v1/carts/${cartId}/tax-lines`)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
get events() {
|
|
88
|
+
return {
|
|
89
|
+
list: (cartId, opts) => {
|
|
90
|
+
const params = new URLSearchParams();
|
|
91
|
+
if (opts?.sinceSequence !== void 0) params.set("sinceSequence", String(opts.sinceSequence));
|
|
92
|
+
if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
|
|
93
|
+
const qs = params.toString();
|
|
94
|
+
return this.request("GET", `/api/v1/carts/${cartId}/events${qs ? "?" + qs : ""}`);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
export {
|
|
100
|
+
CartClient,
|
|
101
|
+
ThrottleApiError
|
|
102
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usethrottle/cart",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Typed REST client for the Throttle Cart API.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": ["dist", "README.md"],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsup": "^8.0.0",
|
|
24
|
+
"vitest": "^2.1.9",
|
|
25
|
+
"typescript": "^5.7.0"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
}
|
|
30
|
+
}
|