@hanzo/commerce.js 1.0.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 ADDED
@@ -0,0 +1,62 @@
1
+ # @hanzo/commerce.js
2
+
3
+ Modern e-commerce framework for React/Next.js. Wraps @hanzo/commerce with a standalone package.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @hanzo/commerce.js
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```tsx
14
+ import {
15
+ ProductCard,
16
+ AddToCartWidget,
17
+ CartPanel,
18
+ CommerceProvider
19
+ } from '@hanzo/commerce.js'
20
+
21
+ function Shop({ products }) {
22
+ return (
23
+ <CommerceProvider>
24
+ <div className="products">
25
+ {products.map(product => (
26
+ <ProductCard key={product.id} product={product}>
27
+ <AddToCartWidget sku={product.sku} />
28
+ </ProductCard>
29
+ ))}
30
+ </div>
31
+ <CartPanel />
32
+ </CommerceProvider>
33
+ )
34
+ }
35
+ ```
36
+
37
+ ## Components
38
+
39
+ ### Product Display
40
+ - `ProductCard` - Display product information
41
+ - `CarouselBuyCard` - Product card with carousel
42
+ - `Icons` - Commerce icons
43
+
44
+ ### Cart
45
+ - `CartPanel` - Shopping cart sidebar
46
+ - `AddToCartWidget` - Add to cart button with quantity
47
+
48
+ ### Checkout
49
+ - `PaymentStepForm` - Payment form step
50
+ - `ShippingStepForm` - Shipping form step
51
+
52
+ ## Types
53
+
54
+ Import types from the `/types` subpath:
55
+
56
+ ```ts
57
+ import type { Product, LineItem, CartItem } from '@hanzo/commerce.js/types'
58
+ ```
59
+
60
+ ## License
61
+
62
+ BSD-3-Clause
@@ -0,0 +1,163 @@
1
+ /**
2
+ * @hanzo/commerce.js/billing
3
+ *
4
+ * THE canonical billing client for the Hanzo platform.
5
+ * Talks to Commerce API (Go backend) for all billing operations:
6
+ * balance, usage, deposits, subscriptions, checkout, plans.
7
+ *
8
+ * One SDK, one way to do billing. All services use this.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { BillingClient } from '@hanzo/commerce.js/billing'
13
+ *
14
+ * const billing = new BillingClient({
15
+ * commerceUrl: 'https://commerce.hanzo.ai',
16
+ * token: iamAccessToken,
17
+ * })
18
+ *
19
+ * // Check balance
20
+ * const balance = await billing.getBalance('hanzo/alice')
21
+ *
22
+ * // Record usage
23
+ * await billing.addUsageRecord({
24
+ * user: 'hanzo/alice',
25
+ * amount: 150,
26
+ * model: 'claude-opus-4-6',
27
+ * tokens: 5000,
28
+ * })
29
+ *
30
+ * // Subscribe
31
+ * await billing.subscribe({ planId: 'growth', userId: 'hanzo/alice' })
32
+ * ```
33
+ */
34
+ type CommerceConfig = {
35
+ /** Commerce API base URL (e.g. "https://commerce.hanzo.ai"). */
36
+ commerceUrl: string;
37
+ /** Optional IAM access token for authenticated requests. */
38
+ token?: string;
39
+ };
40
+ type Balance = {
41
+ balance: number;
42
+ holds: number;
43
+ available: number;
44
+ };
45
+ type Transaction = {
46
+ id?: string;
47
+ owner?: string;
48
+ type: 'hold' | 'hold-removed' | 'transfer' | 'deposit' | 'withdraw';
49
+ destinationId?: string;
50
+ destinationKind?: string;
51
+ sourceId?: string;
52
+ sourceKind?: string;
53
+ currency: string;
54
+ amount: number;
55
+ tags?: string[];
56
+ expiresAt?: string;
57
+ metadata?: Record<string, unknown>;
58
+ createdAt?: string;
59
+ };
60
+ type Subscription = {
61
+ id?: string;
62
+ planId?: string;
63
+ userId?: string;
64
+ status?: 'trialing' | 'active' | 'past_due' | 'canceled' | 'unpaid' | string;
65
+ billingType?: 'charge_automatically' | 'send_invoice';
66
+ periodStart?: string;
67
+ periodEnd?: string;
68
+ trialStart?: string;
69
+ trialEnd?: string;
70
+ quantity?: number;
71
+ createdAt?: string;
72
+ };
73
+ type Plan = {
74
+ slug?: string;
75
+ name?: string;
76
+ description?: string;
77
+ price?: number;
78
+ currency?: string;
79
+ interval?: 'monthly' | 'yearly' | string;
80
+ intervalCount?: number;
81
+ trialPeriodDays?: number;
82
+ metadata?: Record<string, unknown>;
83
+ };
84
+ type Payment = {
85
+ id?: string;
86
+ orderId?: string;
87
+ userId?: string;
88
+ amount?: number;
89
+ amountRefunded?: number;
90
+ fee?: number;
91
+ currency?: string;
92
+ status?: 'cancelled' | 'credit' | 'disputed' | 'failed' | 'fraudulent' | 'paid' | 'refunded' | 'unpaid' | string;
93
+ captured?: boolean;
94
+ live?: boolean;
95
+ createdAt?: string;
96
+ };
97
+ type UsageRecord = {
98
+ user: string;
99
+ currency?: string;
100
+ amount: number;
101
+ model?: string;
102
+ provider?: string;
103
+ tokens?: number;
104
+ promptTokens?: number;
105
+ completionTokens?: number;
106
+ };
107
+ declare class BillingClient {
108
+ private readonly baseUrl;
109
+ private token;
110
+ constructor(config: CommerceConfig);
111
+ /** Update the auth token (e.g. after IAM token refresh). */
112
+ setToken(token: string): void;
113
+ private request;
114
+ /** Get balance for a user in a specific currency. */
115
+ getBalance(user: string, currency?: string, token?: string): Promise<Balance>;
116
+ /** Get balances across all currencies for a user. */
117
+ getAllBalances(user: string, token?: string): Promise<Record<string, Balance>>;
118
+ /** Record API usage (creates a withdraw transaction). */
119
+ addUsageRecord(record: UsageRecord, token?: string): Promise<Transaction>;
120
+ /** Get usage transactions for a user. */
121
+ getUsageRecords(user: string, currency?: string, token?: string): Promise<Transaction[]>;
122
+ /** Create a deposit (credit) for a user. */
123
+ addDeposit(params: {
124
+ user: string;
125
+ currency?: string;
126
+ amount: number;
127
+ notes?: string;
128
+ tags?: string[];
129
+ expiresIn?: string;
130
+ }, token?: string): Promise<Transaction>;
131
+ /** Grant starter credit ($5 USD, 30-day expiry). */
132
+ grantStarterCredit(user: string, token?: string): Promise<Transaction>;
133
+ /** Create a new subscription. */
134
+ subscribe(params: {
135
+ planId: string;
136
+ userId: string;
137
+ paymentToken?: string;
138
+ }, token?: string): Promise<Subscription>;
139
+ /** Get a subscription by ID. */
140
+ getSubscription(subscriptionId: string, token?: string): Promise<Subscription | null>;
141
+ /** Update a subscription. */
142
+ updateSubscription(subscriptionId: string, update: Partial<Subscription>, token?: string): Promise<Subscription>;
143
+ /** Cancel a subscription. */
144
+ cancelSubscription(subscriptionId: string, token?: string): Promise<void>;
145
+ /** Authorize a payment for an order. */
146
+ authorize(orderId: string, token?: string): Promise<Payment>;
147
+ /** Capture a previously authorized payment. */
148
+ capture(orderId: string, token?: string): Promise<Payment>;
149
+ /** Authorize and capture (full charge). */
150
+ charge(orderId: string, token?: string): Promise<Payment>;
151
+ /** Refund a payment. */
152
+ refund(paymentId: string, token?: string): Promise<Payment>;
153
+ /** Get available plans. */
154
+ getPlans(token?: string): Promise<Plan[]>;
155
+ /** Get a specific plan by slug/ID. */
156
+ getPlan(planId: string, token?: string): Promise<Plan | null>;
157
+ }
158
+ declare class CommerceApiError extends Error {
159
+ readonly status: number;
160
+ constructor(status: number, message: string);
161
+ }
162
+
163
+ export { type Balance, BillingClient, CommerceApiError, type CommerceConfig, type Payment, type Plan, type Subscription, type Transaction, type UsageRecord };
@@ -0,0 +1,163 @@
1
+ /**
2
+ * @hanzo/commerce.js/billing
3
+ *
4
+ * THE canonical billing client for the Hanzo platform.
5
+ * Talks to Commerce API (Go backend) for all billing operations:
6
+ * balance, usage, deposits, subscriptions, checkout, plans.
7
+ *
8
+ * One SDK, one way to do billing. All services use this.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { BillingClient } from '@hanzo/commerce.js/billing'
13
+ *
14
+ * const billing = new BillingClient({
15
+ * commerceUrl: 'https://commerce.hanzo.ai',
16
+ * token: iamAccessToken,
17
+ * })
18
+ *
19
+ * // Check balance
20
+ * const balance = await billing.getBalance('hanzo/alice')
21
+ *
22
+ * // Record usage
23
+ * await billing.addUsageRecord({
24
+ * user: 'hanzo/alice',
25
+ * amount: 150,
26
+ * model: 'claude-opus-4-6',
27
+ * tokens: 5000,
28
+ * })
29
+ *
30
+ * // Subscribe
31
+ * await billing.subscribe({ planId: 'growth', userId: 'hanzo/alice' })
32
+ * ```
33
+ */
34
+ type CommerceConfig = {
35
+ /** Commerce API base URL (e.g. "https://commerce.hanzo.ai"). */
36
+ commerceUrl: string;
37
+ /** Optional IAM access token for authenticated requests. */
38
+ token?: string;
39
+ };
40
+ type Balance = {
41
+ balance: number;
42
+ holds: number;
43
+ available: number;
44
+ };
45
+ type Transaction = {
46
+ id?: string;
47
+ owner?: string;
48
+ type: 'hold' | 'hold-removed' | 'transfer' | 'deposit' | 'withdraw';
49
+ destinationId?: string;
50
+ destinationKind?: string;
51
+ sourceId?: string;
52
+ sourceKind?: string;
53
+ currency: string;
54
+ amount: number;
55
+ tags?: string[];
56
+ expiresAt?: string;
57
+ metadata?: Record<string, unknown>;
58
+ createdAt?: string;
59
+ };
60
+ type Subscription = {
61
+ id?: string;
62
+ planId?: string;
63
+ userId?: string;
64
+ status?: 'trialing' | 'active' | 'past_due' | 'canceled' | 'unpaid' | string;
65
+ billingType?: 'charge_automatically' | 'send_invoice';
66
+ periodStart?: string;
67
+ periodEnd?: string;
68
+ trialStart?: string;
69
+ trialEnd?: string;
70
+ quantity?: number;
71
+ createdAt?: string;
72
+ };
73
+ type Plan = {
74
+ slug?: string;
75
+ name?: string;
76
+ description?: string;
77
+ price?: number;
78
+ currency?: string;
79
+ interval?: 'monthly' | 'yearly' | string;
80
+ intervalCount?: number;
81
+ trialPeriodDays?: number;
82
+ metadata?: Record<string, unknown>;
83
+ };
84
+ type Payment = {
85
+ id?: string;
86
+ orderId?: string;
87
+ userId?: string;
88
+ amount?: number;
89
+ amountRefunded?: number;
90
+ fee?: number;
91
+ currency?: string;
92
+ status?: 'cancelled' | 'credit' | 'disputed' | 'failed' | 'fraudulent' | 'paid' | 'refunded' | 'unpaid' | string;
93
+ captured?: boolean;
94
+ live?: boolean;
95
+ createdAt?: string;
96
+ };
97
+ type UsageRecord = {
98
+ user: string;
99
+ currency?: string;
100
+ amount: number;
101
+ model?: string;
102
+ provider?: string;
103
+ tokens?: number;
104
+ promptTokens?: number;
105
+ completionTokens?: number;
106
+ };
107
+ declare class BillingClient {
108
+ private readonly baseUrl;
109
+ private token;
110
+ constructor(config: CommerceConfig);
111
+ /** Update the auth token (e.g. after IAM token refresh). */
112
+ setToken(token: string): void;
113
+ private request;
114
+ /** Get balance for a user in a specific currency. */
115
+ getBalance(user: string, currency?: string, token?: string): Promise<Balance>;
116
+ /** Get balances across all currencies for a user. */
117
+ getAllBalances(user: string, token?: string): Promise<Record<string, Balance>>;
118
+ /** Record API usage (creates a withdraw transaction). */
119
+ addUsageRecord(record: UsageRecord, token?: string): Promise<Transaction>;
120
+ /** Get usage transactions for a user. */
121
+ getUsageRecords(user: string, currency?: string, token?: string): Promise<Transaction[]>;
122
+ /** Create a deposit (credit) for a user. */
123
+ addDeposit(params: {
124
+ user: string;
125
+ currency?: string;
126
+ amount: number;
127
+ notes?: string;
128
+ tags?: string[];
129
+ expiresIn?: string;
130
+ }, token?: string): Promise<Transaction>;
131
+ /** Grant starter credit ($5 USD, 30-day expiry). */
132
+ grantStarterCredit(user: string, token?: string): Promise<Transaction>;
133
+ /** Create a new subscription. */
134
+ subscribe(params: {
135
+ planId: string;
136
+ userId: string;
137
+ paymentToken?: string;
138
+ }, token?: string): Promise<Subscription>;
139
+ /** Get a subscription by ID. */
140
+ getSubscription(subscriptionId: string, token?: string): Promise<Subscription | null>;
141
+ /** Update a subscription. */
142
+ updateSubscription(subscriptionId: string, update: Partial<Subscription>, token?: string): Promise<Subscription>;
143
+ /** Cancel a subscription. */
144
+ cancelSubscription(subscriptionId: string, token?: string): Promise<void>;
145
+ /** Authorize a payment for an order. */
146
+ authorize(orderId: string, token?: string): Promise<Payment>;
147
+ /** Capture a previously authorized payment. */
148
+ capture(orderId: string, token?: string): Promise<Payment>;
149
+ /** Authorize and capture (full charge). */
150
+ charge(orderId: string, token?: string): Promise<Payment>;
151
+ /** Refund a payment. */
152
+ refund(paymentId: string, token?: string): Promise<Payment>;
153
+ /** Get available plans. */
154
+ getPlans(token?: string): Promise<Plan[]>;
155
+ /** Get a specific plan by slug/ID. */
156
+ getPlan(planId: string, token?: string): Promise<Plan | null>;
157
+ }
158
+ declare class CommerceApiError extends Error {
159
+ readonly status: number;
160
+ constructor(status: number, message: string);
161
+ }
162
+
163
+ export { type Balance, BillingClient, CommerceApiError, type CommerceConfig, type Payment, type Plan, type Subscription, type Transaction, type UsageRecord };
@@ -0,0 +1,200 @@
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/billing.ts
21
+ var billing_exports = {};
22
+ __export(billing_exports, {
23
+ BillingClient: () => BillingClient,
24
+ CommerceApiError: () => CommerceApiError
25
+ });
26
+ module.exports = __toCommonJS(billing_exports);
27
+ var DEFAULT_TIMEOUT_MS = 1e4;
28
+ var BillingClient = class {
29
+ constructor(config) {
30
+ this.baseUrl = config.commerceUrl.replace(/\/+$/, "");
31
+ this.token = config.token;
32
+ }
33
+ /** Update the auth token (e.g. after IAM token refresh). */
34
+ setToken(token) {
35
+ this.token = token;
36
+ }
37
+ async request(path, opts) {
38
+ const url = new URL(path, this.baseUrl);
39
+ if (opts?.params) {
40
+ for (const [k, v] of Object.entries(opts.params)) {
41
+ url.searchParams.set(k, v);
42
+ }
43
+ }
44
+ const controller = new AbortController();
45
+ const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
46
+ const headers = { Accept: "application/json" };
47
+ const authToken = opts?.token ?? this.token;
48
+ if (authToken) headers.Authorization = `Bearer ${authToken}`;
49
+ if (opts?.body) headers["Content-Type"] = "application/json";
50
+ try {
51
+ const res = await fetch(url.toString(), {
52
+ method: opts?.method ?? "GET",
53
+ headers,
54
+ body: opts?.body ? JSON.stringify(opts.body) : void 0,
55
+ signal: controller.signal
56
+ });
57
+ if (!res.ok) {
58
+ const text = await res.text().catch(() => "");
59
+ throw new CommerceApiError(res.status, `${res.statusText}: ${text}`.trim());
60
+ }
61
+ return await res.json();
62
+ } finally {
63
+ clearTimeout(timer);
64
+ }
65
+ }
66
+ // -----------------------------------------------------------------------
67
+ // Balance
68
+ // -----------------------------------------------------------------------
69
+ /** Get balance for a user in a specific currency. */
70
+ async getBalance(user, currency = "usd", token) {
71
+ return this.request("/api/v1/billing/balance", {
72
+ params: { user, currency },
73
+ token
74
+ });
75
+ }
76
+ /** Get balances across all currencies for a user. */
77
+ async getAllBalances(user, token) {
78
+ return this.request("/api/v1/billing/balance/all", {
79
+ params: { user },
80
+ token
81
+ });
82
+ }
83
+ // -----------------------------------------------------------------------
84
+ // Usage
85
+ // -----------------------------------------------------------------------
86
+ /** Record API usage (creates a withdraw transaction). */
87
+ async addUsageRecord(record, token) {
88
+ return this.request("/api/v1/billing/usage", {
89
+ method: "POST",
90
+ body: record,
91
+ token
92
+ });
93
+ }
94
+ /** Get usage transactions for a user. */
95
+ async getUsageRecords(user, currency = "usd", token) {
96
+ return this.request("/api/v1/billing/usage", {
97
+ params: { user, currency },
98
+ token
99
+ });
100
+ }
101
+ // -----------------------------------------------------------------------
102
+ // Deposits / Credits
103
+ // -----------------------------------------------------------------------
104
+ /** Create a deposit (credit) for a user. */
105
+ async addDeposit(params, token) {
106
+ return this.request("/api/v1/billing/deposit", {
107
+ method: "POST",
108
+ body: params,
109
+ token
110
+ });
111
+ }
112
+ /** Grant starter credit ($5 USD, 30-day expiry). */
113
+ async grantStarterCredit(user, token) {
114
+ return this.request("/api/v1/billing/credit", {
115
+ method: "POST",
116
+ body: { user },
117
+ token
118
+ });
119
+ }
120
+ // -----------------------------------------------------------------------
121
+ // Subscriptions
122
+ // -----------------------------------------------------------------------
123
+ /** Create a new subscription. */
124
+ async subscribe(params, token) {
125
+ return this.request("/api/v1/subscribe", {
126
+ method: "POST",
127
+ body: params,
128
+ token
129
+ });
130
+ }
131
+ /** Get a subscription by ID. */
132
+ async getSubscription(subscriptionId, token) {
133
+ try {
134
+ return await this.request(`/api/v1/subscribe/${subscriptionId}`, { token });
135
+ } catch {
136
+ return null;
137
+ }
138
+ }
139
+ /** Update a subscription. */
140
+ async updateSubscription(subscriptionId, update, token) {
141
+ return this.request(`/api/v1/subscribe/${subscriptionId}`, {
142
+ method: "PATCH",
143
+ body: update,
144
+ token
145
+ });
146
+ }
147
+ /** Cancel a subscription. */
148
+ async cancelSubscription(subscriptionId, token) {
149
+ await this.request(`/api/v1/subscribe/${subscriptionId}`, {
150
+ method: "DELETE",
151
+ token
152
+ });
153
+ }
154
+ // -----------------------------------------------------------------------
155
+ // Checkout
156
+ // -----------------------------------------------------------------------
157
+ /** Authorize a payment for an order. */
158
+ async authorize(orderId, token) {
159
+ return this.request(`/api/v1/authorize/${orderId}`, { method: "POST", token });
160
+ }
161
+ /** Capture a previously authorized payment. */
162
+ async capture(orderId, token) {
163
+ return this.request(`/api/v1/capture/${orderId}`, { method: "POST", token });
164
+ }
165
+ /** Authorize and capture (full charge). */
166
+ async charge(orderId, token) {
167
+ return this.request(`/api/v1/charge/${orderId}`, { method: "POST", token });
168
+ }
169
+ /** Refund a payment. */
170
+ async refund(paymentId, token) {
171
+ return this.request(`/api/v1/refund/${paymentId}`, { method: "POST", token });
172
+ }
173
+ // -----------------------------------------------------------------------
174
+ // Plans
175
+ // -----------------------------------------------------------------------
176
+ /** Get available plans. */
177
+ async getPlans(token) {
178
+ return this.request("/api/v1/plan", { token });
179
+ }
180
+ /** Get a specific plan by slug/ID. */
181
+ async getPlan(planId, token) {
182
+ try {
183
+ return await this.request(`/api/v1/plan/${planId}`, { token });
184
+ } catch {
185
+ return null;
186
+ }
187
+ }
188
+ };
189
+ var CommerceApiError = class extends Error {
190
+ constructor(status, message) {
191
+ super(message);
192
+ this.name = "CommerceApiError";
193
+ this.status = status;
194
+ }
195
+ };
196
+ // Annotate the CommonJS export names for ESM import in node:
197
+ 0 && (module.exports = {
198
+ BillingClient,
199
+ CommerceApiError
200
+ });
@@ -0,0 +1,174 @@
1
+ // src/billing.ts
2
+ var DEFAULT_TIMEOUT_MS = 1e4;
3
+ var BillingClient = class {
4
+ constructor(config) {
5
+ this.baseUrl = config.commerceUrl.replace(/\/+$/, "");
6
+ this.token = config.token;
7
+ }
8
+ /** Update the auth token (e.g. after IAM token refresh). */
9
+ setToken(token) {
10
+ this.token = token;
11
+ }
12
+ async request(path, opts) {
13
+ const url = new URL(path, this.baseUrl);
14
+ if (opts?.params) {
15
+ for (const [k, v] of Object.entries(opts.params)) {
16
+ url.searchParams.set(k, v);
17
+ }
18
+ }
19
+ const controller = new AbortController();
20
+ const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
21
+ const headers = { Accept: "application/json" };
22
+ const authToken = opts?.token ?? this.token;
23
+ if (authToken) headers.Authorization = `Bearer ${authToken}`;
24
+ if (opts?.body) headers["Content-Type"] = "application/json";
25
+ try {
26
+ const res = await fetch(url.toString(), {
27
+ method: opts?.method ?? "GET",
28
+ headers,
29
+ body: opts?.body ? JSON.stringify(opts.body) : void 0,
30
+ signal: controller.signal
31
+ });
32
+ if (!res.ok) {
33
+ const text = await res.text().catch(() => "");
34
+ throw new CommerceApiError(res.status, `${res.statusText}: ${text}`.trim());
35
+ }
36
+ return await res.json();
37
+ } finally {
38
+ clearTimeout(timer);
39
+ }
40
+ }
41
+ // -----------------------------------------------------------------------
42
+ // Balance
43
+ // -----------------------------------------------------------------------
44
+ /** Get balance for a user in a specific currency. */
45
+ async getBalance(user, currency = "usd", token) {
46
+ return this.request("/api/v1/billing/balance", {
47
+ params: { user, currency },
48
+ token
49
+ });
50
+ }
51
+ /** Get balances across all currencies for a user. */
52
+ async getAllBalances(user, token) {
53
+ return this.request("/api/v1/billing/balance/all", {
54
+ params: { user },
55
+ token
56
+ });
57
+ }
58
+ // -----------------------------------------------------------------------
59
+ // Usage
60
+ // -----------------------------------------------------------------------
61
+ /** Record API usage (creates a withdraw transaction). */
62
+ async addUsageRecord(record, token) {
63
+ return this.request("/api/v1/billing/usage", {
64
+ method: "POST",
65
+ body: record,
66
+ token
67
+ });
68
+ }
69
+ /** Get usage transactions for a user. */
70
+ async getUsageRecords(user, currency = "usd", token) {
71
+ return this.request("/api/v1/billing/usage", {
72
+ params: { user, currency },
73
+ token
74
+ });
75
+ }
76
+ // -----------------------------------------------------------------------
77
+ // Deposits / Credits
78
+ // -----------------------------------------------------------------------
79
+ /** Create a deposit (credit) for a user. */
80
+ async addDeposit(params, token) {
81
+ return this.request("/api/v1/billing/deposit", {
82
+ method: "POST",
83
+ body: params,
84
+ token
85
+ });
86
+ }
87
+ /** Grant starter credit ($5 USD, 30-day expiry). */
88
+ async grantStarterCredit(user, token) {
89
+ return this.request("/api/v1/billing/credit", {
90
+ method: "POST",
91
+ body: { user },
92
+ token
93
+ });
94
+ }
95
+ // -----------------------------------------------------------------------
96
+ // Subscriptions
97
+ // -----------------------------------------------------------------------
98
+ /** Create a new subscription. */
99
+ async subscribe(params, token) {
100
+ return this.request("/api/v1/subscribe", {
101
+ method: "POST",
102
+ body: params,
103
+ token
104
+ });
105
+ }
106
+ /** Get a subscription by ID. */
107
+ async getSubscription(subscriptionId, token) {
108
+ try {
109
+ return await this.request(`/api/v1/subscribe/${subscriptionId}`, { token });
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+ /** Update a subscription. */
115
+ async updateSubscription(subscriptionId, update, token) {
116
+ return this.request(`/api/v1/subscribe/${subscriptionId}`, {
117
+ method: "PATCH",
118
+ body: update,
119
+ token
120
+ });
121
+ }
122
+ /** Cancel a subscription. */
123
+ async cancelSubscription(subscriptionId, token) {
124
+ await this.request(`/api/v1/subscribe/${subscriptionId}`, {
125
+ method: "DELETE",
126
+ token
127
+ });
128
+ }
129
+ // -----------------------------------------------------------------------
130
+ // Checkout
131
+ // -----------------------------------------------------------------------
132
+ /** Authorize a payment for an order. */
133
+ async authorize(orderId, token) {
134
+ return this.request(`/api/v1/authorize/${orderId}`, { method: "POST", token });
135
+ }
136
+ /** Capture a previously authorized payment. */
137
+ async capture(orderId, token) {
138
+ return this.request(`/api/v1/capture/${orderId}`, { method: "POST", token });
139
+ }
140
+ /** Authorize and capture (full charge). */
141
+ async charge(orderId, token) {
142
+ return this.request(`/api/v1/charge/${orderId}`, { method: "POST", token });
143
+ }
144
+ /** Refund a payment. */
145
+ async refund(paymentId, token) {
146
+ return this.request(`/api/v1/refund/${paymentId}`, { method: "POST", token });
147
+ }
148
+ // -----------------------------------------------------------------------
149
+ // Plans
150
+ // -----------------------------------------------------------------------
151
+ /** Get available plans. */
152
+ async getPlans(token) {
153
+ return this.request("/api/v1/plan", { token });
154
+ }
155
+ /** Get a specific plan by slug/ID. */
156
+ async getPlan(planId, token) {
157
+ try {
158
+ return await this.request(`/api/v1/plan/${planId}`, { token });
159
+ } catch {
160
+ return null;
161
+ }
162
+ }
163
+ };
164
+ var CommerceApiError = class extends Error {
165
+ constructor(status, message) {
166
+ super(message);
167
+ this.name = "CommerceApiError";
168
+ this.status = status;
169
+ }
170
+ };
171
+ export {
172
+ BillingClient,
173
+ CommerceApiError
174
+ };
@@ -0,0 +1,2 @@
1
+ export * from '@hanzo/commerce';
2
+ import '@hanzo/commerce/types';
@@ -0,0 +1,2 @@
1
+ export * from '@hanzo/commerce';
2
+ import '@hanzo/commerce/types';
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
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 __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
15
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
16
+
17
+ // src/index.ts
18
+ var index_exports = {};
19
+ module.exports = __toCommonJS(index_exports);
20
+ __reExport(index_exports, require("@hanzo/commerce"), module.exports);
21
+ // Annotate the CommonJS export names for ESM import in node:
22
+ 0 && (module.exports = {
23
+ ...require("@hanzo/commerce")
24
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ // src/index.ts
2
+ export * from "@hanzo/commerce";
@@ -0,0 +1 @@
1
+ export * from '@hanzo/commerce/types';
@@ -0,0 +1 @@
1
+ export * from '@hanzo/commerce/types';
package/dist/types.js ADDED
@@ -0,0 +1,24 @@
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 __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
15
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
16
+
17
+ // src/types.ts
18
+ var types_exports = {};
19
+ module.exports = __toCommonJS(types_exports);
20
+ __reExport(types_exports, require("@hanzo/commerce/types"), module.exports);
21
+ // Annotate the CommonJS export names for ESM import in node:
22
+ 0 && (module.exports = {
23
+ ...require("@hanzo/commerce/types")
24
+ });
package/dist/types.mjs ADDED
@@ -0,0 +1,2 @@
1
+ // src/types.ts
2
+ export * from "@hanzo/commerce/types";
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@hanzo/commerce.js",
3
+ "version": "1.0.0",
4
+ "description": "Hanzo Commerce SDK - Modern e-commerce framework. Wraps @hanzo/commerce.",
5
+ "publishConfig": {
6
+ "registry": "https://registry.npmjs.org/",
7
+ "access": "public"
8
+ },
9
+ "main": "dist/index.js",
10
+ "module": "dist/index.mjs",
11
+ "types": "dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.mjs",
15
+ "require": "./dist/index.js",
16
+ "types": "./dist/index.d.ts"
17
+ },
18
+ "./types": {
19
+ "import": "./dist/types.mjs",
20
+ "require": "./dist/types.js",
21
+ "types": "./dist/types.d.ts"
22
+ },
23
+ "./billing": {
24
+ "import": "./dist/billing.mjs",
25
+ "require": "./dist/billing.js",
26
+ "types": "./dist/billing.d.ts"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "src"
32
+ ],
33
+ "scripts": {
34
+ "build": "tsup src/index.ts src/types.ts src/billing.ts --format cjs,esm --dts --clean",
35
+ "dev": "tsup src/index.ts src/types.ts src/billing.ts --format cjs,esm --dts --watch",
36
+ "prepublishOnly": "npm run build",
37
+ "typecheck": "tsc --noEmit"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/hanzo-js/commerce.js.git"
42
+ },
43
+ "keywords": [
44
+ "commerce",
45
+ "ecommerce",
46
+ "cart",
47
+ "checkout",
48
+ "payments",
49
+ "hanzo",
50
+ "react",
51
+ "nextjs"
52
+ ],
53
+ "author": "Hanzo AI, Inc. <hi@hanzo.ai>",
54
+ "license": "BSD-3-Clause",
55
+ "bugs": {
56
+ "url": "https://github.com/hanzo-js/commerce.js/issues"
57
+ },
58
+ "homepage": "https://github.com/hanzo-js/commerce.js#readme",
59
+ "dependencies": {
60
+ "@hanzo/commerce": "^7.3.10"
61
+ },
62
+ "devDependencies": {
63
+ "@types/react": "^19.0.0",
64
+ "react": "^19.0.0",
65
+ "tsup": "^8.0.0",
66
+ "typescript": "^5.3.0"
67
+ },
68
+ "peerDependencies": {
69
+ "react": "^18.0.0 || ^19.0.0",
70
+ "next": ">=14.0.0"
71
+ },
72
+ "peerDependenciesMeta": {
73
+ "next": {
74
+ "optional": true
75
+ }
76
+ }
77
+ }
package/src/billing.ts ADDED
@@ -0,0 +1,328 @@
1
+ /**
2
+ * @hanzo/commerce.js/billing
3
+ *
4
+ * THE canonical billing client for the Hanzo platform.
5
+ * Talks to Commerce API (Go backend) for all billing operations:
6
+ * balance, usage, deposits, subscriptions, checkout, plans.
7
+ *
8
+ * One SDK, one way to do billing. All services use this.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { BillingClient } from '@hanzo/commerce.js/billing'
13
+ *
14
+ * const billing = new BillingClient({
15
+ * commerceUrl: 'https://commerce.hanzo.ai',
16
+ * token: iamAccessToken,
17
+ * })
18
+ *
19
+ * // Check balance
20
+ * const balance = await billing.getBalance('hanzo/alice')
21
+ *
22
+ * // Record usage
23
+ * await billing.addUsageRecord({
24
+ * user: 'hanzo/alice',
25
+ * amount: 150,
26
+ * model: 'claude-opus-4-6',
27
+ * tokens: 5000,
28
+ * })
29
+ *
30
+ * // Subscribe
31
+ * await billing.subscribe({ planId: 'growth', userId: 'hanzo/alice' })
32
+ * ```
33
+ */
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Config
37
+ // ---------------------------------------------------------------------------
38
+
39
+ export type CommerceConfig = {
40
+ /** Commerce API base URL (e.g. "https://commerce.hanzo.ai"). */
41
+ commerceUrl: string
42
+ /** Optional IAM access token for authenticated requests. */
43
+ token?: string
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Types
48
+ // ---------------------------------------------------------------------------
49
+
50
+ export type Balance = {
51
+ balance: number
52
+ holds: number
53
+ available: number
54
+ }
55
+
56
+ export type Transaction = {
57
+ id?: string
58
+ owner?: string
59
+ type: 'hold' | 'hold-removed' | 'transfer' | 'deposit' | 'withdraw'
60
+ destinationId?: string
61
+ destinationKind?: string
62
+ sourceId?: string
63
+ sourceKind?: string
64
+ currency: string
65
+ amount: number
66
+ tags?: string[]
67
+ expiresAt?: string
68
+ metadata?: Record<string, unknown>
69
+ createdAt?: string
70
+ }
71
+
72
+ export type Subscription = {
73
+ id?: string
74
+ planId?: string
75
+ userId?: string
76
+ status?: 'trialing' | 'active' | 'past_due' | 'canceled' | 'unpaid' | string
77
+ billingType?: 'charge_automatically' | 'send_invoice'
78
+ periodStart?: string
79
+ periodEnd?: string
80
+ trialStart?: string
81
+ trialEnd?: string
82
+ quantity?: number
83
+ createdAt?: string
84
+ }
85
+
86
+ export type Plan = {
87
+ slug?: string
88
+ name?: string
89
+ description?: string
90
+ price?: number
91
+ currency?: string
92
+ interval?: 'monthly' | 'yearly' | string
93
+ intervalCount?: number
94
+ trialPeriodDays?: number
95
+ metadata?: Record<string, unknown>
96
+ }
97
+
98
+ export type Payment = {
99
+ id?: string
100
+ orderId?: string
101
+ userId?: string
102
+ amount?: number
103
+ amountRefunded?: number
104
+ fee?: number
105
+ currency?: string
106
+ status?: 'cancelled' | 'credit' | 'disputed' | 'failed' | 'fraudulent' | 'paid' | 'refunded' | 'unpaid' | string
107
+ captured?: boolean
108
+ live?: boolean
109
+ createdAt?: string
110
+ }
111
+
112
+ export type UsageRecord = {
113
+ user: string
114
+ currency?: string
115
+ amount: number
116
+ model?: string
117
+ provider?: string
118
+ tokens?: number
119
+ promptTokens?: number
120
+ completionTokens?: number
121
+ }
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Client
125
+ // ---------------------------------------------------------------------------
126
+
127
+ const DEFAULT_TIMEOUT_MS = 10_000
128
+
129
+ export class BillingClient {
130
+ private readonly baseUrl: string
131
+ private token: string | undefined
132
+
133
+ constructor(config: CommerceConfig) {
134
+ this.baseUrl = config.commerceUrl.replace(/\/+$/, '')
135
+ this.token = config.token
136
+ }
137
+
138
+ /** Update the auth token (e.g. after IAM token refresh). */
139
+ setToken(token: string) {
140
+ this.token = token
141
+ }
142
+
143
+ private async request<T>(
144
+ path: string,
145
+ opts?: {
146
+ method?: string
147
+ body?: unknown
148
+ token?: string
149
+ params?: Record<string, string>
150
+ },
151
+ ): Promise<T> {
152
+ const url = new URL(path, this.baseUrl)
153
+ if (opts?.params) {
154
+ for (const [k, v] of Object.entries(opts.params)) {
155
+ url.searchParams.set(k, v)
156
+ }
157
+ }
158
+
159
+ const controller = new AbortController()
160
+ const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS)
161
+
162
+ const headers: Record<string, string> = { Accept: 'application/json' }
163
+ const authToken = opts?.token ?? this.token
164
+ if (authToken) headers.Authorization = `Bearer ${authToken}`
165
+ if (opts?.body) headers['Content-Type'] = 'application/json'
166
+
167
+ try {
168
+ const res = await fetch(url.toString(), {
169
+ method: opts?.method ?? 'GET',
170
+ headers,
171
+ body: opts?.body ? JSON.stringify(opts.body) : undefined,
172
+ signal: controller.signal,
173
+ })
174
+
175
+ if (!res.ok) {
176
+ const text = await res.text().catch(() => '')
177
+ throw new CommerceApiError(res.status, `${res.statusText}: ${text}`.trim())
178
+ }
179
+
180
+ return (await res.json()) as T
181
+ } finally {
182
+ clearTimeout(timer)
183
+ }
184
+ }
185
+
186
+ // -----------------------------------------------------------------------
187
+ // Balance
188
+ // -----------------------------------------------------------------------
189
+
190
+ /** Get balance for a user in a specific currency. */
191
+ async getBalance(user: string, currency = 'usd', token?: string): Promise<Balance> {
192
+ return this.request<Balance>('/api/v1/billing/balance', {
193
+ params: { user, currency }, token,
194
+ })
195
+ }
196
+
197
+ /** Get balances across all currencies for a user. */
198
+ async getAllBalances(user: string, token?: string): Promise<Record<string, Balance>> {
199
+ return this.request<Record<string, Balance>>('/api/v1/billing/balance/all', {
200
+ params: { user }, token,
201
+ })
202
+ }
203
+
204
+ // -----------------------------------------------------------------------
205
+ // Usage
206
+ // -----------------------------------------------------------------------
207
+
208
+ /** Record API usage (creates a withdraw transaction). */
209
+ async addUsageRecord(record: UsageRecord, token?: string): Promise<Transaction> {
210
+ return this.request<Transaction>('/api/v1/billing/usage', {
211
+ method: 'POST', body: record, token,
212
+ })
213
+ }
214
+
215
+ /** Get usage transactions for a user. */
216
+ async getUsageRecords(user: string, currency = 'usd', token?: string): Promise<Transaction[]> {
217
+ return this.request<Transaction[]>('/api/v1/billing/usage', {
218
+ params: { user, currency }, token,
219
+ })
220
+ }
221
+
222
+ // -----------------------------------------------------------------------
223
+ // Deposits / Credits
224
+ // -----------------------------------------------------------------------
225
+
226
+ /** Create a deposit (credit) for a user. */
227
+ async addDeposit(
228
+ params: { user: string; currency?: string; amount: number; notes?: string; tags?: string[]; expiresIn?: string },
229
+ token?: string,
230
+ ): Promise<Transaction> {
231
+ return this.request<Transaction>('/api/v1/billing/deposit', {
232
+ method: 'POST', body: params, token,
233
+ })
234
+ }
235
+
236
+ /** Grant starter credit ($5 USD, 30-day expiry). */
237
+ async grantStarterCredit(user: string, token?: string): Promise<Transaction> {
238
+ return this.request<Transaction>('/api/v1/billing/credit', {
239
+ method: 'POST', body: { user }, token,
240
+ })
241
+ }
242
+
243
+ // -----------------------------------------------------------------------
244
+ // Subscriptions
245
+ // -----------------------------------------------------------------------
246
+
247
+ /** Create a new subscription. */
248
+ async subscribe(params: { planId: string; userId: string; paymentToken?: string }, token?: string): Promise<Subscription> {
249
+ return this.request<Subscription>('/api/v1/subscribe', {
250
+ method: 'POST', body: params, token,
251
+ })
252
+ }
253
+
254
+ /** Get a subscription by ID. */
255
+ async getSubscription(subscriptionId: string, token?: string): Promise<Subscription | null> {
256
+ try {
257
+ return await this.request<Subscription>(`/api/v1/subscribe/${subscriptionId}`, { token })
258
+ } catch { return null }
259
+ }
260
+
261
+ /** Update a subscription. */
262
+ async updateSubscription(subscriptionId: string, update: Partial<Subscription>, token?: string): Promise<Subscription> {
263
+ return this.request<Subscription>(`/api/v1/subscribe/${subscriptionId}`, {
264
+ method: 'PATCH', body: update, token,
265
+ })
266
+ }
267
+
268
+ /** Cancel a subscription. */
269
+ async cancelSubscription(subscriptionId: string, token?: string): Promise<void> {
270
+ await this.request<void>(`/api/v1/subscribe/${subscriptionId}`, {
271
+ method: 'DELETE', token,
272
+ })
273
+ }
274
+
275
+ // -----------------------------------------------------------------------
276
+ // Checkout
277
+ // -----------------------------------------------------------------------
278
+
279
+ /** Authorize a payment for an order. */
280
+ async authorize(orderId: string, token?: string): Promise<Payment> {
281
+ return this.request<Payment>(`/api/v1/authorize/${orderId}`, { method: 'POST', token })
282
+ }
283
+
284
+ /** Capture a previously authorized payment. */
285
+ async capture(orderId: string, token?: string): Promise<Payment> {
286
+ return this.request<Payment>(`/api/v1/capture/${orderId}`, { method: 'POST', token })
287
+ }
288
+
289
+ /** Authorize and capture (full charge). */
290
+ async charge(orderId: string, token?: string): Promise<Payment> {
291
+ return this.request<Payment>(`/api/v1/charge/${orderId}`, { method: 'POST', token })
292
+ }
293
+
294
+ /** Refund a payment. */
295
+ async refund(paymentId: string, token?: string): Promise<Payment> {
296
+ return this.request<Payment>(`/api/v1/refund/${paymentId}`, { method: 'POST', token })
297
+ }
298
+
299
+ // -----------------------------------------------------------------------
300
+ // Plans
301
+ // -----------------------------------------------------------------------
302
+
303
+ /** Get available plans. */
304
+ async getPlans(token?: string): Promise<Plan[]> {
305
+ return this.request<Plan[]>('/api/v1/plan', { token })
306
+ }
307
+
308
+ /** Get a specific plan by slug/ID. */
309
+ async getPlan(planId: string, token?: string): Promise<Plan | null> {
310
+ try {
311
+ return await this.request<Plan>(`/api/v1/plan/${planId}`, { token })
312
+ } catch { return null }
313
+ }
314
+ }
315
+
316
+ // ---------------------------------------------------------------------------
317
+ // Error
318
+ // ---------------------------------------------------------------------------
319
+
320
+ export class CommerceApiError extends Error {
321
+ readonly status: number
322
+
323
+ constructor(status: number, message: string) {
324
+ super(message)
325
+ this.name = 'CommerceApiError'
326
+ this.status = status
327
+ }
328
+ }
package/src/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * commerce.js
3
+ *
4
+ * Hanzo Commerce SDK - Modern e-commerce framework for React/Next.js.
5
+ * This package re-exports @hanzo/commerce for easy standalone usage.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { AddToCartWidget, CartPanel, ProductCard } from 'commerce.js'
10
+ *
11
+ * function ProductPage({ product }) {
12
+ * return (
13
+ * <div>
14
+ * <ProductCard product={product} />
15
+ * <AddToCartWidget sku={product.sku} />
16
+ * </div>
17
+ * )
18
+ * }
19
+ * ```
20
+ */
21
+
22
+ // Re-export everything from @hanzo/commerce
23
+ export * from '@hanzo/commerce'
package/src/types.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * commerce.js/types
3
+ *
4
+ * Type definitions for Hanzo Commerce
5
+ */
6
+
7
+ export * from '@hanzo/commerce/types'