@hanzo/commerce 7.7.1 → 7.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/components/cart/cart-panel/promo-code.d.ts +2 -2
  2. package/dist/service/impls/standalone/index.js +4 -4
  3. package/dist/service/impls/standalone/index.js.map +1 -1
  4. package/package.json +46 -17
  5. package/billing.ts +0 -2
  6. package/client.ts +0 -783
  7. package/components/Icons.tsx +0 -35
  8. package/components/add-to-cart-widget.tsx +0 -195
  9. package/components/buy/buy-card.tsx +0 -269
  10. package/components/buy/carousel-buy-card.tsx +0 -243
  11. package/components/buy/multi-family/all-variants-carousel.tsx +0 -259
  12. package/components/buy/multi-family/family-carousel/index.tsx +0 -75
  13. package/components/buy/multi-family/family-carousel/slide.tsx +0 -82
  14. package/components/buy/multi-family/family-carousel/state.ts +0 -87
  15. package/components/buy/multi-family/index.ts +0 -2
  16. package/components/buy/single-family-selector.tsx +0 -91
  17. package/components/buy/title-and-byline.tsx +0 -26
  18. package/components/cart/cart-panel/cart-line-item.tsx +0 -77
  19. package/components/cart/cart-panel/index.tsx +0 -157
  20. package/components/cart/cart-panel/promo-code.tsx +0 -105
  21. package/components/cart/cart-panel/total-area.tsx +0 -61
  22. package/components/checkout/payment-step-form/card-icon-row.tsx +0 -26
  23. package/components/checkout/payment-step-form/card-icons/amex.tsx +0 -32
  24. package/components/checkout/payment-step-form/card-icons/diners-club.tsx +0 -13
  25. package/components/checkout/payment-step-form/card-icons/discover.tsx +0 -25
  26. package/components/checkout/payment-step-form/card-icons/jcb.tsx +0 -26
  27. package/components/checkout/payment-step-form/card-icons/mastercard.tsx +0 -27
  28. package/components/checkout/payment-step-form/card-icons/visa.tsx +0 -25
  29. package/components/checkout/payment-step-form/cc-button.tsx +0 -18
  30. package/components/checkout/payment-step-form/contact-form.tsx +0 -48
  31. package/components/checkout/payment-step-form/crypto-icons/btc.tsx +0 -11
  32. package/components/checkout/payment-step-form/crypto-icons/eth.tsx +0 -20
  33. package/components/checkout/payment-step-form/crypto-icons/usdt.tsx +0 -13
  34. package/components/checkout/payment-step-form/index.tsx +0 -113
  35. package/components/checkout/payment-step-form/methods/bank-transfer.tsx +0 -81
  36. package/components/checkout/payment-step-form/methods/card.tsx +0 -253
  37. package/components/checkout/payment-step-form/methods/crypto.tsx +0 -226
  38. package/components/checkout/payment-step-form/methods/index.ts +0 -23
  39. package/components/checkout/shipping-step-form.tsx +0 -171
  40. package/components/index.ts +0 -11
  41. package/components/item/product-card.tsx +0 -66
  42. package/components/item-selector/button.tsx +0 -213
  43. package/components/item-selector/carousel/index.tsx +0 -196
  44. package/components/item-selector/carousel/slider.tsx +0 -37
  45. package/components/item-selector/index.ts +0 -5
  46. package/components/item-selector/quantity-indicator.tsx +0 -50
  47. package/components/node-tabs/index.tsx +0 -90
  48. package/components/node-tabs/node-image.tsx +0 -31
  49. package/index.ts +0 -55
  50. package/metering.test.ts +0 -199
  51. package/metering.ts +0 -289
  52. package/product-card.test.tsx +0 -104
  53. package/service/context.tsx +0 -45
  54. package/service/debug.ts +0 -41
  55. package/service/impls/standalone/actual-line-item.ts +0 -136
  56. package/service/impls/standalone/get-instance.ts +0 -64
  57. package/service/impls/standalone/index.ts +0 -579
  58. package/service/impls/standalone/order/firebase.ts +0 -5
  59. package/service/impls/standalone/order/index.ts +0 -58
  60. package/service/impls/standalone/persistence.ts +0 -33
  61. package/service/path-utils.ts +0 -26
  62. package/service/sep.ts +0 -7
  63. package/tsconfig.json +0 -17
  64. package/types/README.md +0 -2
  65. package/types/category-node.ts +0 -50
  66. package/types/checkout.ts +0 -47
  67. package/types/commerce-config.ts +0 -13
  68. package/types/commerce-service.ts +0 -128
  69. package/types/family.ts +0 -26
  70. package/types/index.ts +0 -15
  71. package/types/item-selector.ts +0 -97
  72. package/types/line-item.ts +0 -29
  73. package/types/multi-family-selector-props.ts +0 -20
  74. package/types/product.ts +0 -21
  75. package/types/promo.ts +0 -10
  76. package/types/selection-ui-specifier.ts +0 -52
  77. package/types/string-mutator.ts +0 -14
  78. package/types/token-separators.ts +0 -7
  79. package/util/analytics.ts +0 -21
  80. package/util/countries.ts +0 -196
  81. package/util/error.ts +0 -34
  82. package/util/index.ts +0 -71
  83. package/util/item-selector-options-accessor.ts +0 -35
  84. package/util/line-item-ref.ts +0 -23
  85. package/util/multi-family-selector-options-accessor.ts +0 -15
  86. package/util/obs-string-mutator.ts +0 -22
  87. package/util/product-media-accessor.ts +0 -58
  88. package/util/promo-codes.ts +0 -106
  89. package/util/selection-ui-specifiers.ts +0 -30
  90. package/util/square-payment.ts +0 -50
  91. 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
- }