@kaching.sh/sdk 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kaching
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # @kaching.sh/sdk
2
+
3
+ Typed client for the [kaching](https://kaching.sh) commerce API — products, carts, Stripe checkout,
4
+ orders and digital downloads.
5
+
6
+ ```ts
7
+ import { createKaching, formatMoney } from "@kaching.sh/sdk";
8
+
9
+ const kaching = createKaching({ apiKey: process.env.KACHING_SECRET_KEY });
10
+ const { data: products } = await kaching.products.list();
11
+ const cart = await kaching.carts.create({ items: [{ variant_id: products[0].variants[0].id }] });
12
+ const { url } = await kaching.carts.checkout(cart.id, {
13
+ success_url: "https://shop.example/checkout/success",
14
+ cancel_url: "https://shop.example/cart",
15
+ });
16
+ ```
17
+
18
+ Use a **publishable key** (`kc_pk_…`) in browsers — it can read the catalog and create carts and
19
+ checkouts. Use a **secret key** (`kc_sk_…`) only on servers. Money is always an integer in minor
20
+ units; format it with `formatMoney(amount, currency)`.
@@ -0,0 +1,328 @@
1
+ type ProductType = "physical" | "digital";
2
+ type ProductStatus = "draft" | "active" | "archived";
3
+ interface List<T> {
4
+ object: "list";
5
+ data: T[];
6
+ total?: number;
7
+ has_more?: boolean;
8
+ }
9
+ interface Store {
10
+ id: string;
11
+ object: "store";
12
+ name: string;
13
+ slug: string;
14
+ currency: string;
15
+ support_email: string | null;
16
+ storefront_url: string | null;
17
+ livemode: boolean;
18
+ payments: PaymentsStatus;
19
+ tax_enabled: boolean;
20
+ created_at: string;
21
+ }
22
+ interface PaymentsStatus {
23
+ connected: boolean;
24
+ card_payments_status: "active" | "pending" | "restricted" | "unsupported" | null;
25
+ ready: boolean;
26
+ checked_at: string | null;
27
+ }
28
+ interface CreatedStore extends Store {
29
+ /** Returned once at creation. Store them — they can't be retrieved again. */
30
+ keys: {
31
+ publishable: string;
32
+ secret: string;
33
+ };
34
+ }
35
+ interface Variant {
36
+ id: string;
37
+ object: "variant";
38
+ product_id: string;
39
+ title: string;
40
+ sku: string | null;
41
+ price: number;
42
+ compare_at_price: number | null;
43
+ options: Record<string, string>;
44
+ available: boolean;
45
+ position: number;
46
+ /** Secret key only */
47
+ track_inventory?: boolean;
48
+ /** Secret key only */
49
+ inventory_quantity?: number;
50
+ /** Secret key only */
51
+ weight_grams?: number | null;
52
+ /** Secret key only */
53
+ files?: DigitalFile[];
54
+ }
55
+ interface Product {
56
+ id: string;
57
+ object: "product";
58
+ title: string;
59
+ slug: string;
60
+ description: string | null;
61
+ type: ProductType;
62
+ status: ProductStatus;
63
+ images: string[];
64
+ metadata: Record<string, unknown>;
65
+ price_range: {
66
+ min: number;
67
+ max: number;
68
+ } | null;
69
+ variants: Variant[];
70
+ created_at: string;
71
+ updated_at: string;
72
+ }
73
+ interface DigitalFile {
74
+ id: string;
75
+ filename: string;
76
+ content_type: string | null;
77
+ size_bytes: number | null;
78
+ }
79
+ interface ShippingRate {
80
+ id: string;
81
+ object: "shipping_rate";
82
+ name: string;
83
+ amount: number;
84
+ countries: string[];
85
+ min_delivery_days: number | null;
86
+ max_delivery_days: number | null;
87
+ active: boolean;
88
+ created_at: string;
89
+ }
90
+ interface CartItem {
91
+ id: string;
92
+ variant_id: string;
93
+ product_id: string;
94
+ product_slug: string;
95
+ title: string;
96
+ variant_title: string | null;
97
+ options: Record<string, string>;
98
+ type: ProductType;
99
+ image: string | null;
100
+ unit_price: number;
101
+ quantity: number;
102
+ line_total: number;
103
+ available: boolean;
104
+ }
105
+ interface Cart {
106
+ id: string;
107
+ object: "cart";
108
+ status: "open" | "checking_out" | "completed" | "abandoned";
109
+ currency: string;
110
+ email: string | null;
111
+ items: CartItem[];
112
+ item_count: number;
113
+ subtotal: number;
114
+ requires_shipping: boolean;
115
+ created_at: string;
116
+ updated_at: string;
117
+ }
118
+ interface Checkout {
119
+ object: "checkout";
120
+ cart_id: string;
121
+ session_id: string;
122
+ /** Stripe Checkout URL — redirect the shopper here. */
123
+ url: string;
124
+ expires_at: number;
125
+ }
126
+ interface OrderItem {
127
+ id: string;
128
+ variant_id: string | null;
129
+ title: string;
130
+ variant_title: string | null;
131
+ sku: string | null;
132
+ type: ProductType;
133
+ unit_price: number;
134
+ quantity: number;
135
+ line_total: number;
136
+ downloads: {
137
+ filename: string;
138
+ url: string;
139
+ downloads_remaining: number;
140
+ expires_at: string;
141
+ }[];
142
+ }
143
+ interface Order {
144
+ id: string;
145
+ object: "order";
146
+ number: number;
147
+ status: "paid" | "fulfilled" | "refunded" | "canceled";
148
+ fulfillment_status: "unfulfilled" | "fulfilled" | "not_required";
149
+ email: string | null;
150
+ customer_name: string | null;
151
+ shipping_address: Record<string, string | null> | null;
152
+ shipping_rate_name: string | null;
153
+ currency: string;
154
+ subtotal: number;
155
+ shipping_total: number;
156
+ tax_total: number;
157
+ total: number;
158
+ items: OrderItem[];
159
+ created_at: string;
160
+ /** Secret key only */
161
+ application_fee?: number;
162
+ }
163
+ interface OnboardingLink {
164
+ object: "onboarding_link";
165
+ /** Stripe's single-use link; expires within minutes. */
166
+ url: string;
167
+ expires_at: string;
168
+ /** Durable (7 days) kaching link that opens a fresh Stripe onboarding page. Give this to humans. */
169
+ start_url: string;
170
+ }
171
+ interface VariantInput {
172
+ title?: string;
173
+ sku?: string | null;
174
+ price: number;
175
+ compare_at_price?: number | null;
176
+ options?: Record<string, string>;
177
+ track_inventory?: boolean;
178
+ inventory_quantity?: number;
179
+ weight_grams?: number | null;
180
+ position?: number;
181
+ }
182
+ interface ProductInput {
183
+ title: string;
184
+ slug?: string;
185
+ description?: string | null;
186
+ type?: ProductType;
187
+ status?: ProductStatus;
188
+ images?: string[];
189
+ metadata?: Record<string, unknown>;
190
+ /** Shortcut for single-variant products */
191
+ price?: number;
192
+ variants?: VariantInput[];
193
+ }
194
+ interface ShippingRateInput {
195
+ name: string;
196
+ amount: number;
197
+ /** ISO-3166 alpha-2 codes. Omit or empty for worldwide. */
198
+ countries?: string[];
199
+ min_delivery_days?: number | null;
200
+ max_delivery_days?: number | null;
201
+ active?: boolean;
202
+ }
203
+ interface CartItemInput {
204
+ variant_id: string;
205
+ quantity?: number;
206
+ }
207
+ interface CheckoutInput {
208
+ success_url: string;
209
+ cancel_url: string;
210
+ email?: string;
211
+ }
212
+
213
+ declare const DEFAULT_BASE_URL = "https://kaching.sh/api/v1";
214
+ declare class KachingError extends Error {
215
+ status: number;
216
+ code: string;
217
+ details?: unknown | undefined;
218
+ constructor(status: number, code: string, message: string, details?: unknown | undefined);
219
+ }
220
+ interface KachingOptions {
221
+ /** kc_pk_... (browser-safe: catalog, carts, checkout) or kc_sk_... (server/agent: everything). */
222
+ apiKey?: string;
223
+ /** Clerk session token — only for account-level calls like `stores.create`. */
224
+ sessionToken?: string;
225
+ baseUrl?: string;
226
+ fetch?: typeof fetch;
227
+ }
228
+ declare function createKaching(options?: KachingOptions): {
229
+ stores: {
230
+ /** Requires a Clerk session token. */
231
+ list: () => Promise<List<Store>>;
232
+ /** Requires a Clerk session token. Returns API keys once. */
233
+ create: (input: {
234
+ name: string;
235
+ slug?: string;
236
+ currency?: string;
237
+ support_email?: string;
238
+ storefront_url?: string;
239
+ }) => Promise<CreatedStore>;
240
+ };
241
+ store: {
242
+ get: () => Promise<Store>;
243
+ update: (input: Partial<Pick<Store, "name" | "currency" | "support_email" | "storefront_url" | "tax_enabled">>) => Promise<Store>;
244
+ };
245
+ payments: {
246
+ status: () => Promise<PaymentsStatus>;
247
+ /** Creates the Stripe account if needed and returns onboarding links. */
248
+ onboard: (input?: {
249
+ email?: string;
250
+ country?: string;
251
+ }) => Promise<OnboardingLink>;
252
+ };
253
+ products: {
254
+ list: (query?: {
255
+ status?: string;
256
+ type?: string;
257
+ limit?: number;
258
+ offset?: number;
259
+ }) => Promise<List<Product>>;
260
+ /** By id or slug. */
261
+ get: (idOrSlug: string) => Promise<Product>;
262
+ create: (input: ProductInput) => Promise<Product>;
263
+ update: (idOrSlug: string, input: Partial<Omit<ProductInput, "price" | "variants">>) => Promise<Product>;
264
+ delete: (idOrSlug: string) => Promise<{
265
+ id: string;
266
+ deleted: true;
267
+ }>;
268
+ addVariant: (idOrSlug: string, input: VariantInput) => Promise<Product>;
269
+ /** Uploads an image (≤4 MB) and appends it to the product. */
270
+ uploadImage: (idOrSlug: string, file: Blob, filename?: string) => Promise<Product>;
271
+ };
272
+ variants: {
273
+ update: (id: string, input: Partial<VariantInput>) => Promise<Product>;
274
+ delete: (id: string) => Promise<{
275
+ id: string;
276
+ deleted: true;
277
+ }>;
278
+ files: {
279
+ list: (variantId: string) => Promise<List<DigitalFile>>;
280
+ /** Registers a file and uploads its bytes via the signed URL. */
281
+ upload: (variantId: string, file: Blob, filename: string) => Promise<DigitalFile>;
282
+ };
283
+ };
284
+ shippingRates: {
285
+ list: () => Promise<List<ShippingRate>>;
286
+ create: (input: ShippingRateInput) => Promise<ShippingRate>;
287
+ update: (id: string, input: Partial<ShippingRateInput>) => Promise<ShippingRate>;
288
+ delete: (id: string) => Promise<{
289
+ id: string;
290
+ deleted: true;
291
+ }>;
292
+ };
293
+ carts: {
294
+ create: (input?: {
295
+ items?: CartItemInput[];
296
+ email?: string;
297
+ }) => Promise<Cart>;
298
+ get: (id: string) => Promise<Cart>;
299
+ /** Adds to the existing quantity. */
300
+ addItem: (id: string, item: CartItemInput) => Promise<Cart>;
301
+ /** Sets the quantity; 0 removes the line. */
302
+ updateItem: (id: string, variantId: string, quantity: number) => Promise<Cart>;
303
+ removeItem: (id: string, variantId: string) => Promise<Cart>;
304
+ checkout: (id: string, input: CheckoutInput) => Promise<Checkout>;
305
+ };
306
+ checkout: {
307
+ /** "Buy now": creates a cart and a checkout in one call. */
308
+ create: (input: CheckoutInput & {
309
+ items: CartItemInput[];
310
+ }) => Promise<Checkout>;
311
+ /** The order for a completed Checkout Session. 404 until the payment webhook lands — poll briefly. */
312
+ getOrder: (sessionId: string) => Promise<Order>;
313
+ };
314
+ orders: {
315
+ list: (query?: {
316
+ fulfillment_status?: string;
317
+ limit?: number;
318
+ offset?: number;
319
+ }) => Promise<List<Order>>;
320
+ get: (id: string) => Promise<Order>;
321
+ fulfill: (id: string) => Promise<Order>;
322
+ };
323
+ };
324
+ type Kaching = ReturnType<typeof createKaching>;
325
+ /** Formats minor units: formatMoney(24900, "nok") → "NOK 249.00" (locale-aware). */
326
+ declare function formatMoney(amount: number, currency: string, locale?: string): string;
327
+
328
+ export { type Cart, type CartItem, type CartItemInput, type Checkout, type CheckoutInput, type CreatedStore, DEFAULT_BASE_URL, type DigitalFile, type Kaching, KachingError, type KachingOptions, type List, type OnboardingLink, type Order, type OrderItem, type PaymentsStatus, type Product, type ProductInput, type ProductStatus, type ProductType, type ShippingRate, type ShippingRateInput, type Store, type Variant, type VariantInput, createKaching, formatMoney };
package/dist/index.js ADDED
@@ -0,0 +1,137 @@
1
+ // src/index.ts
2
+ var DEFAULT_BASE_URL = "https://kaching.sh/api/v1";
3
+ var KachingError = class extends Error {
4
+ constructor(status, code, message, details) {
5
+ super(message);
6
+ this.status = status;
7
+ this.code = code;
8
+ this.details = details;
9
+ this.name = "KachingError";
10
+ }
11
+ status;
12
+ code;
13
+ details;
14
+ };
15
+ function createKaching(options = {}) {
16
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
17
+ const doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
18
+ const token = options.apiKey ?? options.sessionToken;
19
+ async function request(method, path, body, query) {
20
+ const url = new URL(baseUrl + path);
21
+ for (const [k, v] of Object.entries(query ?? {})) if (v !== void 0) url.searchParams.set(k, String(v));
22
+ const isForm = typeof FormData !== "undefined" && body instanceof FormData;
23
+ const res = await doFetch(url, {
24
+ method,
25
+ headers: {
26
+ ...token && { Authorization: `Bearer ${token}` },
27
+ ...body !== void 0 && !isForm && { "Content-Type": "application/json" }
28
+ },
29
+ body: body === void 0 ? void 0 : isForm ? body : JSON.stringify(body),
30
+ cache: "no-store"
31
+ });
32
+ const data = await res.json().catch(() => null);
33
+ if (!res.ok) {
34
+ const err = data?.error;
35
+ throw new KachingError(res.status, err?.code ?? "http_error", err?.message ?? `Request failed (${res.status})`, err?.details);
36
+ }
37
+ return data;
38
+ }
39
+ const get = (path, query) => request("GET", path, void 0, query);
40
+ const post = (path, body) => request("POST", path, body ?? {});
41
+ const patch = (path, body) => request("PATCH", path, body);
42
+ const del = (path) => request("DELETE", path);
43
+ const enc = encodeURIComponent;
44
+ return {
45
+ stores: {
46
+ /** Requires a Clerk session token. */
47
+ list: () => get("/stores"),
48
+ /** Requires a Clerk session token. Returns API keys once. */
49
+ create: (input) => post("/stores", input)
50
+ },
51
+ store: {
52
+ get: () => get("/store"),
53
+ update: (input) => patch("/store", input)
54
+ },
55
+ payments: {
56
+ status: () => get("/store/payments"),
57
+ /** Creates the Stripe account if needed and returns onboarding links. */
58
+ onboard: (input = {}) => post("/store/payments", input)
59
+ },
60
+ products: {
61
+ list: (query = {}) => get("/products", query),
62
+ /** By id or slug. */
63
+ get: (idOrSlug) => get(`/products/${enc(idOrSlug)}`),
64
+ create: (input) => post("/products", input),
65
+ update: (idOrSlug, input) => patch(`/products/${enc(idOrSlug)}`, input),
66
+ delete: (idOrSlug) => del(`/products/${enc(idOrSlug)}`),
67
+ addVariant: (idOrSlug, input) => post(`/products/${enc(idOrSlug)}/variants`, input),
68
+ /** Uploads an image (≤4 MB) and appends it to the product. */
69
+ uploadImage: (idOrSlug, file, filename = "image") => {
70
+ const form = new FormData();
71
+ form.append("file", file, filename);
72
+ return request("POST", `/products/${enc(idOrSlug)}/images`, form);
73
+ }
74
+ },
75
+ variants: {
76
+ update: (id, input) => patch(`/variants/${enc(id)}`, input),
77
+ delete: (id) => del(`/variants/${enc(id)}`),
78
+ files: {
79
+ list: (variantId) => get(`/variants/${enc(variantId)}/files`),
80
+ /** Registers a file and uploads its bytes via the signed URL. */
81
+ upload: async (variantId, file, filename) => {
82
+ const created = await post(`/variants/${enc(variantId)}/files`, {
83
+ filename,
84
+ content_type: file.type || void 0,
85
+ size_bytes: file.size
86
+ });
87
+ const res = await doFetch(created.upload_url, {
88
+ method: "PUT",
89
+ headers: file.type ? { "Content-Type": file.type } : {},
90
+ body: file
91
+ });
92
+ if (!res.ok) throw new KachingError(res.status, "upload_failed", `File upload failed (${res.status})`);
93
+ const { upload_url: _, ...rest } = created;
94
+ return rest;
95
+ }
96
+ }
97
+ },
98
+ shippingRates: {
99
+ list: () => get("/shipping-rates"),
100
+ create: (input) => post("/shipping-rates", input),
101
+ update: (id, input) => patch(`/shipping-rates/${enc(id)}`, input),
102
+ delete: (id) => del(`/shipping-rates/${enc(id)}`)
103
+ },
104
+ carts: {
105
+ create: (input = {}) => post("/carts", input),
106
+ get: (id) => get(`/carts/${enc(id)}`),
107
+ /** Adds to the existing quantity. */
108
+ addItem: (id, item) => post(`/carts/${enc(id)}/items`, item),
109
+ /** Sets the quantity; 0 removes the line. */
110
+ updateItem: (id, variantId, quantity) => patch(`/carts/${enc(id)}/items/${enc(variantId)}`, { quantity }),
111
+ removeItem: (id, variantId) => del(`/carts/${enc(id)}/items/${enc(variantId)}`),
112
+ checkout: (id, input) => post(`/carts/${enc(id)}/checkout`, input)
113
+ },
114
+ checkout: {
115
+ /** "Buy now": creates a cart and a checkout in one call. */
116
+ create: (input) => post("/checkout", input),
117
+ /** The order for a completed Checkout Session. 404 until the payment webhook lands — poll briefly. */
118
+ getOrder: (sessionId) => get(`/checkout/sessions/${enc(sessionId)}`)
119
+ },
120
+ orders: {
121
+ list: (query = {}) => get("/orders", query),
122
+ get: (id) => get(`/orders/${enc(id)}`),
123
+ fulfill: (id) => post(`/orders/${enc(id)}/fulfill`)
124
+ }
125
+ };
126
+ }
127
+ function formatMoney(amount, currency, locale) {
128
+ const code = currency.toUpperCase();
129
+ const digits = new Intl.NumberFormat("en", { style: "currency", currency: code }).resolvedOptions().maximumFractionDigits ?? 2;
130
+ return new Intl.NumberFormat(locale, { style: "currency", currency: code }).format(amount / 10 ** digits);
131
+ }
132
+ export {
133
+ DEFAULT_BASE_URL,
134
+ KachingError,
135
+ createKaching,
136
+ formatMoney
137
+ };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@kaching.sh/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Typed client for the kaching commerce API",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "LICENSE",
16
+ "README.md"
17
+ ],
18
+ "devDependencies": {
19
+ "tsup": "^8",
20
+ "typescript": "^5"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/sventhaman/Kaching.git",
25
+ "directory": "packages/js"
26
+ },
27
+ "homepage": "https://kaching.sh",
28
+ "bugs": "https://github.com/sventhaman/Kaching/issues",
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "keywords": [
33
+ "kaching",
34
+ "ecommerce",
35
+ "commerce",
36
+ "stripe",
37
+ "checkout",
38
+ "sdk"
39
+ ],
40
+ "engines": {
41
+ "node": ">=20"
42
+ },
43
+ "scripts": {
44
+ "build": "tsup src/index.ts --format esm --dts --clean",
45
+ "dev": "tsup src/index.ts --format esm --dts --watch",
46
+ "typecheck": "tsc --noEmit"
47
+ }
48
+ }