@hanzo/commerce 7.7.1 → 7.7.2
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/package.json +38 -9
- package/billing.ts +0 -2
- package/client.ts +0 -783
- package/components/Icons.tsx +0 -35
- package/components/add-to-cart-widget.tsx +0 -195
- package/components/buy/buy-card.tsx +0 -269
- package/components/buy/carousel-buy-card.tsx +0 -243
- package/components/buy/multi-family/all-variants-carousel.tsx +0 -259
- package/components/buy/multi-family/family-carousel/index.tsx +0 -75
- package/components/buy/multi-family/family-carousel/slide.tsx +0 -82
- package/components/buy/multi-family/family-carousel/state.ts +0 -87
- package/components/buy/multi-family/index.ts +0 -2
- package/components/buy/single-family-selector.tsx +0 -91
- package/components/buy/title-and-byline.tsx +0 -26
- package/components/cart/cart-panel/cart-line-item.tsx +0 -77
- package/components/cart/cart-panel/index.tsx +0 -157
- package/components/cart/cart-panel/promo-code.tsx +0 -105
- package/components/cart/cart-panel/total-area.tsx +0 -61
- package/components/checkout/payment-step-form/card-icon-row.tsx +0 -26
- package/components/checkout/payment-step-form/card-icons/amex.tsx +0 -32
- package/components/checkout/payment-step-form/card-icons/diners-club.tsx +0 -13
- package/components/checkout/payment-step-form/card-icons/discover.tsx +0 -25
- package/components/checkout/payment-step-form/card-icons/jcb.tsx +0 -26
- package/components/checkout/payment-step-form/card-icons/mastercard.tsx +0 -27
- package/components/checkout/payment-step-form/card-icons/visa.tsx +0 -25
- package/components/checkout/payment-step-form/cc-button.tsx +0 -18
- package/components/checkout/payment-step-form/contact-form.tsx +0 -48
- package/components/checkout/payment-step-form/crypto-icons/btc.tsx +0 -11
- package/components/checkout/payment-step-form/crypto-icons/eth.tsx +0 -20
- package/components/checkout/payment-step-form/crypto-icons/usdt.tsx +0 -13
- package/components/checkout/payment-step-form/index.tsx +0 -113
- package/components/checkout/payment-step-form/methods/bank-transfer.tsx +0 -81
- package/components/checkout/payment-step-form/methods/card.tsx +0 -253
- package/components/checkout/payment-step-form/methods/crypto.tsx +0 -226
- package/components/checkout/payment-step-form/methods/index.ts +0 -23
- package/components/checkout/shipping-step-form.tsx +0 -171
- package/components/index.ts +0 -11
- package/components/item/product-card.tsx +0 -66
- package/components/item-selector/button.tsx +0 -213
- package/components/item-selector/carousel/index.tsx +0 -196
- package/components/item-selector/carousel/slider.tsx +0 -37
- package/components/item-selector/index.ts +0 -5
- package/components/item-selector/quantity-indicator.tsx +0 -50
- package/components/node-tabs/index.tsx +0 -90
- package/components/node-tabs/node-image.tsx +0 -31
- package/index.ts +0 -55
- package/metering.test.ts +0 -199
- package/metering.ts +0 -289
- package/product-card.test.tsx +0 -104
- package/service/context.tsx +0 -45
- package/service/debug.ts +0 -41
- package/service/impls/standalone/actual-line-item.ts +0 -136
- package/service/impls/standalone/get-instance.ts +0 -64
- package/service/impls/standalone/index.ts +0 -579
- package/service/impls/standalone/order/firebase.ts +0 -5
- package/service/impls/standalone/order/index.ts +0 -58
- package/service/impls/standalone/persistence.ts +0 -33
- package/service/path-utils.ts +0 -26
- package/service/sep.ts +0 -7
- package/tsconfig.json +0 -17
- package/types/README.md +0 -2
- package/types/category-node.ts +0 -50
- package/types/checkout.ts +0 -47
- package/types/commerce-config.ts +0 -13
- package/types/commerce-service.ts +0 -128
- package/types/family.ts +0 -26
- package/types/index.ts +0 -15
- package/types/item-selector.ts +0 -97
- package/types/line-item.ts +0 -29
- package/types/multi-family-selector-props.ts +0 -20
- package/types/product.ts +0 -21
- package/types/promo.ts +0 -10
- package/types/selection-ui-specifier.ts +0 -52
- package/types/string-mutator.ts +0 -14
- package/types/token-separators.ts +0 -7
- package/util/analytics.ts +0 -21
- package/util/countries.ts +0 -196
- package/util/error.ts +0 -34
- package/util/index.ts +0 -71
- package/util/item-selector-options-accessor.ts +0 -35
- package/util/line-item-ref.ts +0 -23
- package/util/multi-family-selector-options-accessor.ts +0 -15
- package/util/obs-string-mutator.ts +0 -22
- package/util/product-media-accessor.ts +0 -58
- package/util/promo-codes.ts +0 -106
- package/util/selection-ui-specifiers.ts +0 -30
- package/util/square-payment.ts +0 -50
- package/util/use-sync-sku-param-w-current-item.ts +0 -88
package/client.ts
DELETED
|
@@ -1,783 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @hanzo/commerce/client
|
|
3
|
-
*
|
|
4
|
-
* Universal TypeScript client for the Hanzo Commerce API.
|
|
5
|
-
* Works in browser, Node.js, and edge runtimes — no backend required.
|
|
6
|
-
* Covers billing, subscriptions, payments, checkout, coupons,
|
|
7
|
-
* referrals, affiliates, usage, and plans.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* import { Commerce } from '@hanzo/commerce/client'
|
|
12
|
-
*
|
|
13
|
-
* const commerce = new Commerce({ baseUrl: 'https://api.hanzo.ai', token: iamToken })
|
|
14
|
-
*
|
|
15
|
-
* // Validate a coupon before checkout
|
|
16
|
-
* const coupon = await commerce.validateCoupon('LAUNCH50')
|
|
17
|
-
*
|
|
18
|
-
* // Create a checkout session
|
|
19
|
-
* const session = await commerce.createCheckoutSession({
|
|
20
|
-
* items: [{ id: 'plan_pro', quantity: 1 }],
|
|
21
|
-
* couponCode: 'LAUNCH50',
|
|
22
|
-
* referrerId: 'ref_abc123',
|
|
23
|
-
* successUrl: 'https://app.example.com/success',
|
|
24
|
-
* cancelUrl: 'https://app.example.com/cancel',
|
|
25
|
-
* })
|
|
26
|
-
* window.location.href = session.checkoutUrl
|
|
27
|
-
*
|
|
28
|
-
* // Subscribe
|
|
29
|
-
* const sub = await commerce.subscribe({ planId: 'pro', userId: 'user_xyz' })
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
// Config
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
|
|
37
|
-
export type CommerceClientConfig = {
|
|
38
|
-
/**
|
|
39
|
-
* Commerce API base URL.
|
|
40
|
-
* Defaults to https://api.hanzo.ai
|
|
41
|
-
*/
|
|
42
|
-
baseUrl?: string
|
|
43
|
-
/** @deprecated use baseUrl */
|
|
44
|
-
commerceUrl?: string
|
|
45
|
-
/** IAM access token for authenticated requests. */
|
|
46
|
-
token?: string
|
|
47
|
-
/** Request timeout in milliseconds. Default 15 000. */
|
|
48
|
-
timeoutMs?: number
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// Core types
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
|
-
|
|
55
|
-
export type Balance = {
|
|
56
|
-
balance: number
|
|
57
|
-
holds: number
|
|
58
|
-
available: number
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Tier-aware balance from GET /v1/billing/tier. effectiveAvailable folds the
|
|
63
|
-
* tenant's included plan allotment (e.g. the free-tier daily credit) into the
|
|
64
|
-
* prepaid available balance — gate on this to honor included usage first.
|
|
65
|
-
*/
|
|
66
|
-
export type TierResponse = {
|
|
67
|
-
user: string
|
|
68
|
-
tier: {
|
|
69
|
-
name: string
|
|
70
|
-
displayName?: string
|
|
71
|
-
maxAgents?: number
|
|
72
|
-
unlimitedAgents?: boolean
|
|
73
|
-
dailyCreditsCents?: number
|
|
74
|
-
allowedModels?: string[]
|
|
75
|
-
}
|
|
76
|
-
balance: {
|
|
77
|
-
currency: string
|
|
78
|
-
prepaidAvailable: number
|
|
79
|
-
dailyRemaining: number
|
|
80
|
-
effectiveAvailable: number
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export type Transaction = {
|
|
85
|
-
id?: string
|
|
86
|
-
owner?: string
|
|
87
|
-
type: 'hold' | 'hold-removed' | 'transfer' | 'deposit' | 'withdraw'
|
|
88
|
-
destinationId?: string
|
|
89
|
-
destinationKind?: string
|
|
90
|
-
sourceId?: string
|
|
91
|
-
sourceKind?: string
|
|
92
|
-
currency: string
|
|
93
|
-
amount: number
|
|
94
|
-
tags?: string[]
|
|
95
|
-
expiresAt?: string
|
|
96
|
-
metadata?: Record<string, unknown>
|
|
97
|
-
createdAt?: string
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export type Subscription = {
|
|
101
|
-
id?: string
|
|
102
|
-
planId?: string
|
|
103
|
-
userId?: string
|
|
104
|
-
customerId?: string
|
|
105
|
-
status?: 'trialing' | 'active' | 'past_due' | 'canceled' | 'unpaid' | string
|
|
106
|
-
billingType?: 'charge_automatically' | 'send_invoice'
|
|
107
|
-
periodStart?: string
|
|
108
|
-
periodEnd?: string
|
|
109
|
-
trialStart?: string
|
|
110
|
-
trialEnd?: string
|
|
111
|
-
quantity?: number
|
|
112
|
-
createdAt?: string
|
|
113
|
-
cancelAtPeriodEnd?: boolean
|
|
114
|
-
currentPeriodEnd?: string
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export type Plan = {
|
|
118
|
-
id?: string
|
|
119
|
-
slug?: string
|
|
120
|
-
name?: string
|
|
121
|
-
description?: string
|
|
122
|
-
price?: number
|
|
123
|
-
priceMonthly?: number
|
|
124
|
-
priceAnnual?: number
|
|
125
|
-
currency?: string
|
|
126
|
-
interval?: 'monthly' | 'yearly' | string
|
|
127
|
-
intervalCount?: number
|
|
128
|
-
trialPeriodDays?: number
|
|
129
|
-
features?: string[]
|
|
130
|
-
popular?: boolean
|
|
131
|
-
contactSales?: boolean
|
|
132
|
-
metadata?: Record<string, unknown>
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export type Payment = {
|
|
136
|
-
id?: string
|
|
137
|
-
orderId?: string
|
|
138
|
-
userId?: string
|
|
139
|
-
amount?: number
|
|
140
|
-
amountRefunded?: number
|
|
141
|
-
fee?: number
|
|
142
|
-
currency?: string
|
|
143
|
-
status?: 'cancelled' | 'credit' | 'disputed' | 'failed' | 'fraudulent' | 'paid' | 'refunded' | 'unpaid' | string
|
|
144
|
-
captured?: boolean
|
|
145
|
-
live?: boolean
|
|
146
|
-
createdAt?: string
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export type UsageRecord = {
|
|
150
|
-
user: string
|
|
151
|
-
currency?: string
|
|
152
|
-
amount: number
|
|
153
|
-
model?: string
|
|
154
|
-
provider?: string
|
|
155
|
-
tokens?: number
|
|
156
|
-
promptTokens?: number
|
|
157
|
-
completionTokens?: number
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
|
-
// Coupon / Discount types
|
|
162
|
-
// ---------------------------------------------------------------------------
|
|
163
|
-
|
|
164
|
-
export type CouponType = 'Percent' | 'Flat' | 'FreeShipping' | 'FreeItem'
|
|
165
|
-
|
|
166
|
-
export type Coupon = {
|
|
167
|
-
id?: string
|
|
168
|
-
code: string
|
|
169
|
-
type: CouponType
|
|
170
|
-
/** Amount: percentage (0-100) for Percent, cents for Flat */
|
|
171
|
-
amount: number
|
|
172
|
-
description?: string
|
|
173
|
-
limit?: number
|
|
174
|
-
used?: number
|
|
175
|
-
startDate?: string
|
|
176
|
-
endDate?: string
|
|
177
|
-
enabled?: boolean
|
|
178
|
-
/** Calculated discount in cents (returned by validateCoupon) */
|
|
179
|
-
discountCents?: number
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export type CouponValidateResult = {
|
|
183
|
-
valid: boolean
|
|
184
|
-
coupon?: Coupon
|
|
185
|
-
error?: string
|
|
186
|
-
/** Discount in cents for a given subtotal */
|
|
187
|
-
discountCents?: number
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export type Discount = {
|
|
191
|
-
id?: string
|
|
192
|
-
type: 'Percent' | 'Flat' | 'FreeShipping' | 'FreeItem' | 'Bulk'
|
|
193
|
-
amount: number
|
|
194
|
-
scope?: 'Product' | 'Variant' | 'Collection' | 'Store'
|
|
195
|
-
enabled?: boolean
|
|
196
|
-
startDate?: string
|
|
197
|
-
endDate?: string
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// ---------------------------------------------------------------------------
|
|
201
|
-
// Checkout types
|
|
202
|
-
// ---------------------------------------------------------------------------
|
|
203
|
-
|
|
204
|
-
export type CheckoutItem = {
|
|
205
|
-
id: string
|
|
206
|
-
quantity?: number
|
|
207
|
-
/** Price override in cents */
|
|
208
|
-
price?: number
|
|
209
|
-
name?: string
|
|
210
|
-
description?: string
|
|
211
|
-
imageUrl?: string
|
|
212
|
-
metadata?: Record<string, unknown>
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export type CheckoutSessionRequest = {
|
|
216
|
-
items: CheckoutItem[]
|
|
217
|
-
successUrl: string
|
|
218
|
-
cancelUrl: string
|
|
219
|
-
couponCode?: string
|
|
220
|
-
referrerId?: string
|
|
221
|
-
affiliateId?: string
|
|
222
|
-
currency?: string
|
|
223
|
-
customer?: {
|
|
224
|
-
email?: string
|
|
225
|
-
name?: string
|
|
226
|
-
address?: string
|
|
227
|
-
city?: string
|
|
228
|
-
zip?: string
|
|
229
|
-
}
|
|
230
|
-
metadata?: Record<string, unknown>
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export type CheckoutSessionResponse = {
|
|
234
|
-
checkoutUrl: string
|
|
235
|
-
sessionId: string
|
|
236
|
-
/** Original total in cents before discount */
|
|
237
|
-
originalTotal?: number
|
|
238
|
-
/** Final total in cents after discount */
|
|
239
|
-
finalTotal?: number
|
|
240
|
-
discount?: {
|
|
241
|
-
code: string
|
|
242
|
-
type: CouponType
|
|
243
|
-
amount: number
|
|
244
|
-
discountCents: number
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// ---------------------------------------------------------------------------
|
|
249
|
-
// Payment method types
|
|
250
|
-
// ---------------------------------------------------------------------------
|
|
251
|
-
|
|
252
|
-
export type PaymentMethodType = 'card' | 'bank_account' | 'balance' | 'crypto' | 'wire'
|
|
253
|
-
|
|
254
|
-
export type PaymentMethod = {
|
|
255
|
-
id: string
|
|
256
|
-
type: PaymentMethodType
|
|
257
|
-
isDefault?: boolean
|
|
258
|
-
customerId?: string
|
|
259
|
-
card?: {
|
|
260
|
-
brand: string
|
|
261
|
-
last4: string
|
|
262
|
-
expMonth: number
|
|
263
|
-
expYear: number
|
|
264
|
-
}
|
|
265
|
-
bank_account?: {
|
|
266
|
-
bankName?: string
|
|
267
|
-
last4?: string
|
|
268
|
-
accountType?: 'checking' | 'savings'
|
|
269
|
-
routingNumber?: string
|
|
270
|
-
}
|
|
271
|
-
crypto?: {
|
|
272
|
-
chain: string
|
|
273
|
-
address: string
|
|
274
|
-
label?: string
|
|
275
|
-
}
|
|
276
|
-
providerRef?: string
|
|
277
|
-
providerType?: string
|
|
278
|
-
createdAt?: string
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// ---------------------------------------------------------------------------
|
|
282
|
-
// Referral / Affiliate types
|
|
283
|
-
// ---------------------------------------------------------------------------
|
|
284
|
-
|
|
285
|
-
export type Referral = {
|
|
286
|
-
id?: string
|
|
287
|
-
userId?: string
|
|
288
|
-
referrerId?: string
|
|
289
|
-
affiliateId?: string
|
|
290
|
-
orderId?: string
|
|
291
|
-
fee?: number
|
|
292
|
-
createdAt?: string
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
export type Referrer = {
|
|
296
|
-
id?: string
|
|
297
|
-
userId?: string
|
|
298
|
-
enabled?: boolean
|
|
299
|
-
code?: string
|
|
300
|
-
referrals?: Referral[]
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export type Affiliate = {
|
|
304
|
-
id?: string
|
|
305
|
-
userId?: string
|
|
306
|
-
enabled?: boolean
|
|
307
|
-
commission?: number
|
|
308
|
-
couponId?: string
|
|
309
|
-
connectUrl?: string
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// ---------------------------------------------------------------------------
|
|
313
|
-
// Credit grant
|
|
314
|
-
// ---------------------------------------------------------------------------
|
|
315
|
-
|
|
316
|
-
export type CreditGrant = {
|
|
317
|
-
id?: string
|
|
318
|
-
userId?: string
|
|
319
|
-
amount: number
|
|
320
|
-
currency: string
|
|
321
|
-
expiresAt?: string
|
|
322
|
-
tags?: string[]
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// ---------------------------------------------------------------------------
|
|
326
|
-
// Client
|
|
327
|
-
// ---------------------------------------------------------------------------
|
|
328
|
-
|
|
329
|
-
const DEFAULT_BASE_URL = 'https://api.hanzo.ai'
|
|
330
|
-
const DEFAULT_TIMEOUT_MS = 15_000
|
|
331
|
-
|
|
332
|
-
export class Commerce {
|
|
333
|
-
private readonly baseUrl: string
|
|
334
|
-
private token: string | undefined
|
|
335
|
-
private readonly timeoutMs: number
|
|
336
|
-
|
|
337
|
-
constructor(config: CommerceClientConfig = {}) {
|
|
338
|
-
this.baseUrl = (config.baseUrl ?? config.commerceUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '')
|
|
339
|
-
this.token = config.token
|
|
340
|
-
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/** Update the auth token (e.g. after IAM token refresh). */
|
|
344
|
-
setToken(token: string): void {
|
|
345
|
-
this.token = token
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
private async request<T>(
|
|
349
|
-
path: string,
|
|
350
|
-
opts?: {
|
|
351
|
-
method?: string
|
|
352
|
-
body?: unknown
|
|
353
|
-
token?: string
|
|
354
|
-
params?: Record<string, string>
|
|
355
|
-
/** Extra request headers, e.g. X-IAM-Org-Id for S2S calls. */
|
|
356
|
-
headers?: Record<string, string>
|
|
357
|
-
},
|
|
358
|
-
): Promise<T> {
|
|
359
|
-
const url = new URL(path, this.baseUrl)
|
|
360
|
-
if (opts?.params) {
|
|
361
|
-
for (const [k, v] of Object.entries(opts.params)) {
|
|
362
|
-
url.searchParams.set(k, v)
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const controller = new AbortController()
|
|
367
|
-
const timer = setTimeout(() => controller.abort(), this.timeoutMs)
|
|
368
|
-
|
|
369
|
-
const headers: Record<string, string> = { Accept: 'application/json' }
|
|
370
|
-
const authToken = opts?.token ?? this.token
|
|
371
|
-
if (authToken) headers['Authorization'] = `Bearer ${authToken}`
|
|
372
|
-
if (opts?.body) headers['Content-Type'] = 'application/json'
|
|
373
|
-
if (opts?.headers) Object.assign(headers, opts.headers)
|
|
374
|
-
|
|
375
|
-
try {
|
|
376
|
-
const res = await fetch(url.toString(), {
|
|
377
|
-
method: opts?.method ?? 'GET',
|
|
378
|
-
headers,
|
|
379
|
-
body: opts?.body ? JSON.stringify(opts.body) : undefined,
|
|
380
|
-
signal: controller.signal,
|
|
381
|
-
})
|
|
382
|
-
|
|
383
|
-
if (!res.ok) {
|
|
384
|
-
const text = await res.text().catch(() => '')
|
|
385
|
-
throw new CommerceApiError(res.status, `${res.statusText}: ${text}`.trim())
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return (await res.json()) as T
|
|
389
|
-
} finally {
|
|
390
|
-
clearTimeout(timer)
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// -----------------------------------------------------------------------
|
|
395
|
-
// Balance
|
|
396
|
-
// -----------------------------------------------------------------------
|
|
397
|
-
|
|
398
|
-
async getBalance(user: string, currency = 'usd', token?: string, headers?: Record<string, string>): Promise<Balance> {
|
|
399
|
-
return this.request<Balance>('/v1/billing/balance', {
|
|
400
|
-
params: { user, currency }, token, headers,
|
|
401
|
-
})
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
async getAllBalances(user: string, token?: string): Promise<Record<string, Balance>> {
|
|
405
|
-
return this.request<Record<string, Balance>>('/v1/billing/balance/all', {
|
|
406
|
-
params: { user }, token,
|
|
407
|
-
})
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Tier-aware balance: prepaid available plus the tenant's included plan
|
|
412
|
-
* allotment (effectiveAvailable). Use this for the metering gate when you
|
|
413
|
-
* want included usage (e.g. free-tier daily credit) honored before prepaid
|
|
414
|
-
* funds are drawn down.
|
|
415
|
-
*/
|
|
416
|
-
async getTier(user: string, opts?: { token?: string; headers?: Record<string, string> }): Promise<TierResponse> {
|
|
417
|
-
return this.request<TierResponse>('/v1/billing/tier', {
|
|
418
|
-
params: { user }, token: opts?.token, headers: opts?.headers,
|
|
419
|
-
})
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// -----------------------------------------------------------------------
|
|
423
|
-
// Usage
|
|
424
|
-
// -----------------------------------------------------------------------
|
|
425
|
-
|
|
426
|
-
async addUsageRecord(record: UsageRecord, token?: string, headers?: Record<string, string>): Promise<Transaction> {
|
|
427
|
-
return this.request<Transaction>('/v1/billing/usage', {
|
|
428
|
-
method: 'POST', body: record, token, headers,
|
|
429
|
-
})
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
async getUsageRecords(user: string, currency = 'usd', token?: string): Promise<Transaction[]> {
|
|
433
|
-
return this.request<Transaction[]>('/v1/billing/usage', {
|
|
434
|
-
params: { user, currency }, token,
|
|
435
|
-
})
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// -----------------------------------------------------------------------
|
|
439
|
-
// Deposits / Credits
|
|
440
|
-
// -----------------------------------------------------------------------
|
|
441
|
-
|
|
442
|
-
async addDeposit(
|
|
443
|
-
params: { user: string; currency?: string; amount: number; notes?: string; tags?: string[]; expiresIn?: string },
|
|
444
|
-
token?: string,
|
|
445
|
-
): Promise<Transaction> {
|
|
446
|
-
return this.request<Transaction>('/v1/billing/deposit', {
|
|
447
|
-
method: 'POST', body: params, token,
|
|
448
|
-
})
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
async grantStarterCredit(user: string, token?: string): Promise<Transaction> {
|
|
452
|
-
return this.request<Transaction>('/v1/billing/credit', {
|
|
453
|
-
method: 'POST', body: { user }, token,
|
|
454
|
-
})
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// -----------------------------------------------------------------------
|
|
458
|
-
// Plans
|
|
459
|
-
// -----------------------------------------------------------------------
|
|
460
|
-
|
|
461
|
-
async getPlans(token?: string): Promise<Plan[]> {
|
|
462
|
-
return this.request<Plan[]>('/v1/billing/plans', { token })
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
async getPlan(planId: string, token?: string): Promise<Plan | null> {
|
|
466
|
-
try {
|
|
467
|
-
return await this.request<Plan>(`/v1/billing/plans/${planId}`, { token })
|
|
468
|
-
} catch { return null }
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// -----------------------------------------------------------------------
|
|
472
|
-
// Subscriptions
|
|
473
|
-
// -----------------------------------------------------------------------
|
|
474
|
-
|
|
475
|
-
async subscribe(
|
|
476
|
-
params: { planId: string; userId?: string; customerId?: string; paymentMethodId?: string; couponCode?: string; trialDays?: number },
|
|
477
|
-
token?: string,
|
|
478
|
-
): Promise<Subscription> {
|
|
479
|
-
return this.request<Subscription>('/v1/billing/subscriptions', {
|
|
480
|
-
method: 'POST', body: params, token,
|
|
481
|
-
})
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
async getSubscription(subscriptionId: string, token?: string): Promise<Subscription | null> {
|
|
485
|
-
try {
|
|
486
|
-
return await this.request<Subscription>(`/v1/billing/subscriptions/${subscriptionId}`, { token })
|
|
487
|
-
} catch { return null }
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
async listSubscriptions(params?: { customerId?: string }, token?: string): Promise<Subscription[]> {
|
|
491
|
-
return this.request<Subscription[]>('/v1/billing/subscriptions', {
|
|
492
|
-
params: params as Record<string, string> | undefined, token,
|
|
493
|
-
})
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
async updateSubscription(subscriptionId: string, update: Partial<Subscription>, token?: string): Promise<Subscription> {
|
|
497
|
-
return this.request<Subscription>(`/v1/billing/subscriptions/${subscriptionId}`, {
|
|
498
|
-
method: 'PATCH', body: update, token,
|
|
499
|
-
})
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
async cancelSubscription(subscriptionId: string, immediately = false, token?: string): Promise<Subscription> {
|
|
503
|
-
return this.request<Subscription>(`/v1/billing/subscriptions/${subscriptionId}/cancel`, {
|
|
504
|
-
method: 'POST', body: { immediately }, token,
|
|
505
|
-
})
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
async reactivateSubscription(subscriptionId: string, token?: string): Promise<Subscription> {
|
|
509
|
-
return this.request<Subscription>(`/v1/billing/subscriptions/${subscriptionId}/reactivate`, {
|
|
510
|
-
method: 'POST', token,
|
|
511
|
-
})
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// -----------------------------------------------------------------------
|
|
515
|
-
// Checkout sessions
|
|
516
|
-
// -----------------------------------------------------------------------
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* Create a hosted checkout session.
|
|
520
|
-
* Returns a URL to redirect the customer to for payment.
|
|
521
|
-
* Supports coupons, referral tracking, and multiple currencies.
|
|
522
|
-
*/
|
|
523
|
-
async createCheckoutSession(
|
|
524
|
-
params: CheckoutSessionRequest,
|
|
525
|
-
token?: string,
|
|
526
|
-
): Promise<CheckoutSessionResponse> {
|
|
527
|
-
return this.request<CheckoutSessionResponse>('/v1/checkout/sessions', {
|
|
528
|
-
method: 'POST', body: params, token,
|
|
529
|
-
})
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// -----------------------------------------------------------------------
|
|
533
|
-
// Payment methods
|
|
534
|
-
// -----------------------------------------------------------------------
|
|
535
|
-
|
|
536
|
-
async addPaymentMethod(
|
|
537
|
-
params: {
|
|
538
|
-
customerId: string
|
|
539
|
-
type: PaymentMethodType
|
|
540
|
-
token?: string // provider nonce (Square Web Payments SDK)
|
|
541
|
-
providerRef?: string
|
|
542
|
-
providerType?: string
|
|
543
|
-
},
|
|
544
|
-
token?: string,
|
|
545
|
-
): Promise<PaymentMethod> {
|
|
546
|
-
return this.request<PaymentMethod>('/v1/billing/methods', {
|
|
547
|
-
method: 'POST', body: params, token,
|
|
548
|
-
})
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
async listPaymentMethods(customerId: string, token?: string): Promise<PaymentMethod[]> {
|
|
552
|
-
return this.request<PaymentMethod[]>('/v1/billing/methods', {
|
|
553
|
-
params: { customerId }, token,
|
|
554
|
-
})
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
async removePaymentMethod(paymentMethodId: string, token?: string): Promise<void> {
|
|
558
|
-
await this.request<void>(`/v1/billing/methods/${paymentMethodId}`, {
|
|
559
|
-
method: 'DELETE', token,
|
|
560
|
-
})
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
async setDefaultPaymentMethod(customerId: string, paymentMethodId: string, token?: string): Promise<PaymentMethod> {
|
|
564
|
-
return this.request<PaymentMethod>(`/v1/billing/customers/${customerId}/default-payment-method`, {
|
|
565
|
-
method: 'POST', body: { paymentMethodId }, token,
|
|
566
|
-
})
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
// -----------------------------------------------------------------------
|
|
570
|
-
// Coupons / Promo codes
|
|
571
|
-
// -----------------------------------------------------------------------
|
|
572
|
-
|
|
573
|
-
/**
|
|
574
|
-
* Validate a coupon code.
|
|
575
|
-
* Optionally pass a subtotalCents to get the calculated discount amount.
|
|
576
|
-
*/
|
|
577
|
-
async validateCoupon(code: string, subtotalCents?: number, token?: string): Promise<CouponValidateResult> {
|
|
578
|
-
try {
|
|
579
|
-
const result = await this.request<Coupon>('/v1/coupon/validate', {
|
|
580
|
-
method: 'POST',
|
|
581
|
-
body: { code: code.toUpperCase().trim(), subtotalCents },
|
|
582
|
-
token,
|
|
583
|
-
})
|
|
584
|
-
return { valid: true, coupon: result }
|
|
585
|
-
} catch (err) {
|
|
586
|
-
const msg = err instanceof CommerceApiError ? err.message : 'Invalid coupon'
|
|
587
|
-
return { valid: false, error: msg }
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/**
|
|
592
|
-
* Redeem a coupon for a user. Creates credit grant records.
|
|
593
|
-
*/
|
|
594
|
-
async redeemCoupon(code: string, userId: string, token?: string): Promise<CreditGrant[]> {
|
|
595
|
-
return this.request<CreditGrant[]>('/v1/coupon/redeem', {
|
|
596
|
-
method: 'POST',
|
|
597
|
-
body: { code: code.toUpperCase().trim(), userId },
|
|
598
|
-
token,
|
|
599
|
-
})
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// -----------------------------------------------------------------------
|
|
603
|
-
// Referrals & Affiliates
|
|
604
|
-
// -----------------------------------------------------------------------
|
|
605
|
-
|
|
606
|
-
/**
|
|
607
|
-
* Get or create a referrer record for a user.
|
|
608
|
-
* Returns the referral code/link the user can share.
|
|
609
|
-
*/
|
|
610
|
-
async getOrCreateReferrer(userId: string, token?: string): Promise<Referrer> {
|
|
611
|
-
return this.request<Referrer>('/v1/referrer', {
|
|
612
|
-
method: 'POST', body: { userId }, token,
|
|
613
|
-
})
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
async getReferrals(userId: string, token?: string): Promise<Referral[]> {
|
|
617
|
-
return this.request<Referral[]>(`/v1/user/${userId}/referrals`, { token })
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
async getReferrers(userId: string, token?: string): Promise<Referrer[]> {
|
|
621
|
-
return this.request<Referrer[]>(`/v1/user/${userId}/referrers`, { token })
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
/**
|
|
625
|
-
* Get affiliate details for a user.
|
|
626
|
-
*/
|
|
627
|
-
async getAffiliate(userId: string, token?: string): Promise<Affiliate | null> {
|
|
628
|
-
try {
|
|
629
|
-
return await this.request<Affiliate>(`/v1/user/${userId}/affiliate`, { token })
|
|
630
|
-
} catch { return null }
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
/**
|
|
634
|
-
* Create an affiliate account for a user.
|
|
635
|
-
* After creation, user can connect their bank via the returnedconnectUrl.
|
|
636
|
-
*/
|
|
637
|
-
async createAffiliate(userId: string, token?: string): Promise<Affiliate> {
|
|
638
|
-
return this.request<Affiliate>('/v1/affiliate', {
|
|
639
|
-
method: 'POST', body: { userId }, token,
|
|
640
|
-
})
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
async getAffiliateReferrals(affiliateId: string, token?: string): Promise<Referral[]> {
|
|
644
|
-
return this.request<Referral[]>(`/v1/affiliate/${affiliateId}/referrals`, { token })
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
async getAffiliateOrders(affiliateId: string, token?: string): Promise<unknown[]> {
|
|
648
|
-
return this.request<unknown[]>(`/v1/affiliate/${affiliateId}/orders`, { token })
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
async getAffiliateTransactions(affiliateId: string, token?: string): Promise<Transaction[]> {
|
|
652
|
-
return this.request<Transaction[]>(`/v1/affiliate/${affiliateId}/transactions`, { token })
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// -----------------------------------------------------------------------
|
|
656
|
-
// Legacy checkout (order-based)
|
|
657
|
-
// -----------------------------------------------------------------------
|
|
658
|
-
|
|
659
|
-
async authorize(orderId: string, token?: string): Promise<Payment> {
|
|
660
|
-
return this.request<Payment>(`/v1/authorize/${orderId}`, { method: 'POST', token })
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
async capture(orderId: string, token?: string): Promise<Payment> {
|
|
664
|
-
return this.request<Payment>(`/v1/capture/${orderId}`, { method: 'POST', token })
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
async charge(orderId: string, token?: string): Promise<Payment> {
|
|
668
|
-
return this.request<Payment>(`/v1/charge/${orderId}`, { method: 'POST', token })
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
async refund(paymentId: string, token?: string): Promise<Payment> {
|
|
672
|
-
return this.request<Payment>(`/v1/refund/${paymentId}`, { method: 'POST', token })
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
async billingRefund(
|
|
676
|
-
params: { user: string; amount: number; originalTransactionId: string; currency?: string; notes?: string },
|
|
677
|
-
token?: string,
|
|
678
|
-
): Promise<Transaction> {
|
|
679
|
-
return this.request<Transaction>('/v1/billing/refund', {
|
|
680
|
-
method: 'POST', body: params, token,
|
|
681
|
-
})
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// -----------------------------------------------------------------------
|
|
685
|
-
// Crypto wallet deposit address
|
|
686
|
-
// -----------------------------------------------------------------------
|
|
687
|
-
|
|
688
|
-
/**
|
|
689
|
-
* Get or create a deposit address for a given chain.
|
|
690
|
-
* Used for crypto top-up — user sends funds to this address.
|
|
691
|
-
*/
|
|
692
|
-
async getWalletAddress(params: { chain: string; userId: string }, token?: string): Promise<{ address: string; chain: string; qrCode?: string }> {
|
|
693
|
-
return this.request<{ address: string; chain: string; qrCode?: string }>('/api/wallet/account', {
|
|
694
|
-
method: 'POST',
|
|
695
|
-
body: { name: `${params.userId}-${params.chain}`, blockchainType: params.chain },
|
|
696
|
-
token,
|
|
697
|
-
})
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// -----------------------------------------------------------------------
|
|
701
|
-
// Bank account (ACH) and crypto payment methods
|
|
702
|
-
// -----------------------------------------------------------------------
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* Add a bank account payment method (ACH).
|
|
706
|
-
* plaidToken from Plaid Link or Square bank OAuth.
|
|
707
|
-
*/
|
|
708
|
-
async addBankAccount(
|
|
709
|
-
params: {
|
|
710
|
-
customerId: string
|
|
711
|
-
plaidToken?: string // Plaid public token
|
|
712
|
-
bankName?: string
|
|
713
|
-
accountType?: 'checking' | 'savings'
|
|
714
|
-
},
|
|
715
|
-
token?: string,
|
|
716
|
-
): Promise<PaymentMethod> {
|
|
717
|
-
return this.request<PaymentMethod>('/v1/billing/methods', {
|
|
718
|
-
method: 'POST',
|
|
719
|
-
body: { customerId: params.customerId, type: 'bank_account', plaidToken: params.plaidToken, bankName: params.bankName, accountType: params.accountType },
|
|
720
|
-
token,
|
|
721
|
-
})
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
async addCryptoWallet(
|
|
725
|
-
params: { customerId: string; chain: string; address: string; label?: string },
|
|
726
|
-
token?: string,
|
|
727
|
-
): Promise<PaymentMethod> {
|
|
728
|
-
return this.request<PaymentMethod>('/v1/billing/methods', {
|
|
729
|
-
method: 'POST',
|
|
730
|
-
body: { customerId: params.customerId, type: 'crypto', chain: params.chain, address: params.address, label: params.label },
|
|
731
|
-
token,
|
|
732
|
-
})
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// -----------------------------------------------------------------------
|
|
736
|
-
// Top-up
|
|
737
|
-
// -----------------------------------------------------------------------
|
|
738
|
-
|
|
739
|
-
/**
|
|
740
|
-
* Convenience: charge a saved payment method and deposit credits.
|
|
741
|
-
* amount is in cents.
|
|
742
|
-
*/
|
|
743
|
-
async topup(params: { userId: string; paymentMethodId: string; amountCents: number; currency?: string }, token?: string): Promise<Transaction> {
|
|
744
|
-
return this.request<Transaction>('/v1/billing/topup', {
|
|
745
|
-
method: 'POST',
|
|
746
|
-
body: params,
|
|
747
|
-
token,
|
|
748
|
-
})
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
// ---------------------------------------------------------------------------
|
|
753
|
-
// Standalone factory helpers — import these for quick setup
|
|
754
|
-
// ---------------------------------------------------------------------------
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
* Create a commerce client pre-configured for api.hanzo.ai.
|
|
758
|
-
* Pass your IAM access token (read from localStorage or cookie).
|
|
759
|
-
*
|
|
760
|
-
* @example
|
|
761
|
-
* ```ts
|
|
762
|
-
* import { hanzoCommerce } from '@hanzo/commerce/client'
|
|
763
|
-
* const commerce = hanzoCommerce(localStorage.getItem('hanzo-auth-token') ?? undefined)
|
|
764
|
-
* const plans = await commerce.getPlans()
|
|
765
|
-
* ```
|
|
766
|
-
*/
|
|
767
|
-
export function hanzoCommerce(token?: string): Commerce {
|
|
768
|
-
return new Commerce({ token })
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// ---------------------------------------------------------------------------
|
|
772
|
-
// Error
|
|
773
|
-
// ---------------------------------------------------------------------------
|
|
774
|
-
|
|
775
|
-
export class CommerceApiError extends Error {
|
|
776
|
-
readonly status: number
|
|
777
|
-
|
|
778
|
-
constructor(status: number, message: string) {
|
|
779
|
-
super(message)
|
|
780
|
-
this.name = 'CommerceApiError'
|
|
781
|
-
this.status = status
|
|
782
|
-
}
|
|
783
|
-
}
|