@usethrottle/cart 3.10.1 → 3.11.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 CHANGED
@@ -222,23 +222,45 @@ await client.shippingTax.pushExternalSnapshot({
222
222
 
223
223
  ## Subscriptions
224
224
 
225
- Mark a line item with `type: 'subscription'`:
225
+ Put `recurring` on a line to make it a subscription. A cart can hold any
226
+ number of one-time lines and up to 10 subscription lines, with the same plan
227
+ or different ones. The whole cart is paid in one capture, with one shipping
228
+ fee, and completes as one order linked to every subscription it created.
226
229
 
227
230
  ```ts
231
+ await client.items.add(cart.id, { name: 'Coffee kit', unitPrice: 4500, quantity: 1 });
228
232
  await client.items.add(cart.id, {
229
- type: 'subscription',
230
- name: 'Pro Plan',
231
- unitPrice: 1900,
232
- quantity: 1,
233
+ name: 'Barista Club',
234
+ unitPrice: 2000, // per period, per seat
235
+ quantity: 2, // seats
236
+ recurring: { plan: 'club_monthly', interval: 'monthly', trialDays: 14 },
233
237
  });
234
238
  ```
235
239
 
236
- Then call `client.carts.checkout` as normal.
237
-
238
- The billing schedule is **not** settable through this endpoint. The add-item
239
- body is strict and has no `recurring` field, so sending one is rejected with
240
- `Unsupported field(s): recurring` — the recurring plan is attached by the
241
- subscription engine, not by the cart API.
240
+ - `interval` (`weekly`, `biweekly`, `monthly`, `quarterly`, `yearly`), or
241
+ `intervalUnit` (`hour`, `day`, `week`, `month`) + `intervalCount`: 1 hour
242
+ to 1 year.
243
+ - `totalPayments` (2–60) makes an installment plan; `trialDays` (0–365) a free
244
+ trial. They cannot be combined (`400 invalid_combination`).
245
+ - `type` is forced to `subscription`, and the line never ships:
246
+ `requiresShipping: true` is rejected with `400 validation_error`.
247
+ - A free-trial line is stored with `unitPrice: 0` and your price on
248
+ `recurring.amount`, and its name gets a "— 14-day free trial" suffix.
249
+ - `PATCH` may change `quantity` (seats) and `metadata` only. Changing
250
+ `unitPrice`, `taxAmount` or `discountAmount` is `422 recurring_line_immutable`.
251
+ Remove the line and add it again to change its terms.
252
+ - Each line's `subscriptionId` is set when the checkout completes.
253
+
254
+ | Status | Code | When |
255
+ |---|---|---|
256
+ | 422 | `too_many_subscriptions` | An 11th subscription line |
257
+ | 422 | `recurring_not_supported_for_cart` | The cart is managed by an external cart provider |
258
+ | 422 | `recurring_line_immutable` | A PATCH changes a subscription line's price, tax or discount |
259
+ | 400 | `invalid_line_item` | A free-trial line carries `taxAmount` or `discountAmount` |
260
+ | 400 | `invalid_interval` / `interval_conflict` / `invalid_total_payments` / `invalid_combination` / `invalid_plan` / `invalid_trial_days` | Terms out of range |
261
+
262
+ Do not also send a session `recurring` intent for the same cart. The two are
263
+ exclusive, and session creation answers `422 recurring_source_conflict`.
242
264
 
243
265
  ## Events
244
266
 
package/dist/index.d.cts CHANGED
@@ -79,6 +79,47 @@ interface CartSessionCart extends Omit<Cart, 'workspaceId'> {
79
79
  */
80
80
  unitCount: number;
81
81
  }
82
+ type SubscriptionInterval = 'weekly' | 'biweekly' | 'monthly' | 'quarterly' | 'yearly' | 'custom';
83
+ type SubscriptionIntervalUnit = 'hour' | 'day' | 'week' | 'month';
84
+ /**
85
+ * Terms that make a cart line a subscription. One subscription is created per
86
+ * line when the checkout completes, with `quantity` as seats; at most 10 per
87
+ * cart. Send `interval`, or `intervalUnit` + `intervalCount` (1 hour to 1
88
+ * year). `totalPayments` (2-60) makes an installment plan; `trialDays`
89
+ * (0-365) a free trial, not combinable with `totalPayments`. The line's
90
+ * `unitPrice` is the per-period price per seat.
91
+ */
92
+ interface LineRecurringInput {
93
+ plan: string;
94
+ planName?: string;
95
+ interval?: SubscriptionInterval;
96
+ intervalUnit?: SubscriptionIntervalUnit;
97
+ intervalCount?: number;
98
+ totalPayments?: number;
99
+ trialDays?: number;
100
+ }
101
+ /** A merchant-declared subscription line's stored terms (it carries `plan`). */
102
+ interface StoredLineRecurring {
103
+ plan: string;
104
+ planName: string | null;
105
+ interval: string;
106
+ intervalUnit: string;
107
+ intervalCount: number;
108
+ totalPayments: number | null;
109
+ trialDays: number;
110
+ /** Renewal price per seat. Set only on a free-trial line, whose `unitPrice` is 0. */
111
+ amount: number | null;
112
+ /** Display mirror of `totalPayments`. */
113
+ count: number | null;
114
+ }
115
+ /** The plan line Throttle adds for a checkout session `recurring` intent. */
116
+ interface LegacyLineRecurring {
117
+ interval: string;
118
+ intervalUnit?: string | null;
119
+ intervalCount?: number | null;
120
+ /** Display metadata: the number of payments to show. Not enforced. */
121
+ count: number | null;
122
+ }
82
123
  interface LineItem {
83
124
  id: string;
84
125
  cartId: string;
@@ -93,16 +134,13 @@ interface LineItem {
93
134
  discountAmount: number;
94
135
  total: number;
95
136
  /**
96
- * Display metadata. `count` is the number of payments to show, and is not
97
- * enforced. On the plan line Throttle writes for a recurring checkout,
98
- * `intervalUnit`/`intervalCount` describe a custom cadence.
137
+ * Subscription terms. `'plan' in recurring` tells a merchant-declared
138
+ * subscription line (`StoredLineRecurring`) from the plan line a checkout
139
+ * session adds (`LegacyLineRecurring`). Null on a one-time line.
99
140
  */
100
- recurring: {
101
- interval: string;
102
- count: number | null;
103
- intervalUnit?: string | null;
104
- intervalCount?: number | null;
105
- } | null;
141
+ recurring: StoredLineRecurring | LegacyLineRecurring | null;
142
+ /** The subscription this line created. Null until the checkout completes. */
143
+ subscriptionId: string | null;
106
144
  imageUrl: string | null;
107
145
  position: number;
108
146
  metadata: Record<string, unknown>;
@@ -181,16 +219,12 @@ interface AddLineItemInput {
181
219
  taxAmount?: number;
182
220
  discountAmount?: number;
183
221
  /**
184
- * Display metadata. `count` is the number of payments to show, and is not
185
- * enforced. On the plan line Throttle writes for a recurring checkout,
186
- * `intervalUnit`/`intervalCount` describe a custom cadence.
222
+ * Makes this line a subscription: `type` is forced to `subscription`, and it
223
+ * never ships (`requiresShipping: true` is rejected with it). A free-trial
224
+ * line is stored with `unitPrice` 0 and your price on `recurring.amount`.
225
+ * Terms cannot change later: remove the line and add it again.
187
226
  */
188
- recurring?: {
189
- interval: string;
190
- count: number | null;
191
- intervalUnit?: string | null;
192
- intervalCount?: number | null;
193
- } | null;
227
+ recurring?: LineRecurringInput;
194
228
  imageUrl?: string;
195
229
  /**
196
230
  * Whether this item is physically shippable. For external/ad-hoc catalogs
@@ -201,6 +235,11 @@ interface AddLineItemInput {
201
235
  requiresShipping?: boolean;
202
236
  metadata?: Record<string, unknown>;
203
237
  }
238
+ /**
239
+ * On a subscription line only `quantity` (seats) and `metadata` may change;
240
+ * a different `unitPrice`, `taxAmount` or `discountAmount` is
241
+ * 422 `recurring_line_immutable`.
242
+ */
204
243
  interface UpdateLineItemInput {
205
244
  quantity?: number;
206
245
  unitPrice?: number;
@@ -705,4 +744,4 @@ declare function cartErrorFromResponse(args: {
705
744
  responseHeaders?: ThrottleErrorArgs['responseHeaders'];
706
745
  }): ThrottleApiError;
707
746
 
708
- export { type AddLineItemInput, type AppliedDiscount, type Cart, type CartAddress, type CartBranding, CartClient, type CartEvent, CartNotFoundError, CartNotOpenError, type CartSession, type CartSessionCart, CartSessionClient, type CartSessionClientOptions, type CartSessionSummary, CartTimeoutError, type CheckoutHandoff, type CheckoutHandoffInput, type CheckoutInput, type CreateCartInput, type CreateCartSessionInput, type ExternalShippingTaxSnapshotInput, type LineItem, type Order, type SelectShippingInput, type SelectedShipping, type ShippingTaxAddress, type ShippingTaxCalculationResponse, type ShippingTaxCartCalculateInput, type ShippingTaxMethod, type ShippingTaxQuoteInput, type ShippingTaxQuoteItem, StorefrontQuoteClient, type TaxLine, type TaxLineInput, ThrottleApiError, type UpdateCartInput, type UpdateCartSessionInput, type UpdateLineItemInput, cartErrorFromResponse };
747
+ export { type AddLineItemInput, type AppliedDiscount, type Cart, type CartAddress, type CartBranding, CartClient, type CartEvent, CartNotFoundError, CartNotOpenError, type CartSession, type CartSessionCart, CartSessionClient, type CartSessionClientOptions, type CartSessionSummary, CartTimeoutError, type CheckoutHandoff, type CheckoutHandoffInput, type CheckoutInput, type CreateCartInput, type CreateCartSessionInput, type ExternalShippingTaxSnapshotInput, type LegacyLineRecurring, type LineItem, type LineRecurringInput, type Order, type SelectShippingInput, type SelectedShipping, type ShippingTaxAddress, type ShippingTaxCalculationResponse, type ShippingTaxCartCalculateInput, type ShippingTaxMethod, type ShippingTaxQuoteInput, type ShippingTaxQuoteItem, type StoredLineRecurring, StorefrontQuoteClient, type SubscriptionInterval, type SubscriptionIntervalUnit, type TaxLine, type TaxLineInput, ThrottleApiError, type UpdateCartInput, type UpdateCartSessionInput, type UpdateLineItemInput, cartErrorFromResponse };
package/dist/index.d.ts CHANGED
@@ -79,6 +79,47 @@ interface CartSessionCart extends Omit<Cart, 'workspaceId'> {
79
79
  */
80
80
  unitCount: number;
81
81
  }
82
+ type SubscriptionInterval = 'weekly' | 'biweekly' | 'monthly' | 'quarterly' | 'yearly' | 'custom';
83
+ type SubscriptionIntervalUnit = 'hour' | 'day' | 'week' | 'month';
84
+ /**
85
+ * Terms that make a cart line a subscription. One subscription is created per
86
+ * line when the checkout completes, with `quantity` as seats; at most 10 per
87
+ * cart. Send `interval`, or `intervalUnit` + `intervalCount` (1 hour to 1
88
+ * year). `totalPayments` (2-60) makes an installment plan; `trialDays`
89
+ * (0-365) a free trial, not combinable with `totalPayments`. The line's
90
+ * `unitPrice` is the per-period price per seat.
91
+ */
92
+ interface LineRecurringInput {
93
+ plan: string;
94
+ planName?: string;
95
+ interval?: SubscriptionInterval;
96
+ intervalUnit?: SubscriptionIntervalUnit;
97
+ intervalCount?: number;
98
+ totalPayments?: number;
99
+ trialDays?: number;
100
+ }
101
+ /** A merchant-declared subscription line's stored terms (it carries `plan`). */
102
+ interface StoredLineRecurring {
103
+ plan: string;
104
+ planName: string | null;
105
+ interval: string;
106
+ intervalUnit: string;
107
+ intervalCount: number;
108
+ totalPayments: number | null;
109
+ trialDays: number;
110
+ /** Renewal price per seat. Set only on a free-trial line, whose `unitPrice` is 0. */
111
+ amount: number | null;
112
+ /** Display mirror of `totalPayments`. */
113
+ count: number | null;
114
+ }
115
+ /** The plan line Throttle adds for a checkout session `recurring` intent. */
116
+ interface LegacyLineRecurring {
117
+ interval: string;
118
+ intervalUnit?: string | null;
119
+ intervalCount?: number | null;
120
+ /** Display metadata: the number of payments to show. Not enforced. */
121
+ count: number | null;
122
+ }
82
123
  interface LineItem {
83
124
  id: string;
84
125
  cartId: string;
@@ -93,16 +134,13 @@ interface LineItem {
93
134
  discountAmount: number;
94
135
  total: number;
95
136
  /**
96
- * Display metadata. `count` is the number of payments to show, and is not
97
- * enforced. On the plan line Throttle writes for a recurring checkout,
98
- * `intervalUnit`/`intervalCount` describe a custom cadence.
137
+ * Subscription terms. `'plan' in recurring` tells a merchant-declared
138
+ * subscription line (`StoredLineRecurring`) from the plan line a checkout
139
+ * session adds (`LegacyLineRecurring`). Null on a one-time line.
99
140
  */
100
- recurring: {
101
- interval: string;
102
- count: number | null;
103
- intervalUnit?: string | null;
104
- intervalCount?: number | null;
105
- } | null;
141
+ recurring: StoredLineRecurring | LegacyLineRecurring | null;
142
+ /** The subscription this line created. Null until the checkout completes. */
143
+ subscriptionId: string | null;
106
144
  imageUrl: string | null;
107
145
  position: number;
108
146
  metadata: Record<string, unknown>;
@@ -181,16 +219,12 @@ interface AddLineItemInput {
181
219
  taxAmount?: number;
182
220
  discountAmount?: number;
183
221
  /**
184
- * Display metadata. `count` is the number of payments to show, and is not
185
- * enforced. On the plan line Throttle writes for a recurring checkout,
186
- * `intervalUnit`/`intervalCount` describe a custom cadence.
222
+ * Makes this line a subscription: `type` is forced to `subscription`, and it
223
+ * never ships (`requiresShipping: true` is rejected with it). A free-trial
224
+ * line is stored with `unitPrice` 0 and your price on `recurring.amount`.
225
+ * Terms cannot change later: remove the line and add it again.
187
226
  */
188
- recurring?: {
189
- interval: string;
190
- count: number | null;
191
- intervalUnit?: string | null;
192
- intervalCount?: number | null;
193
- } | null;
227
+ recurring?: LineRecurringInput;
194
228
  imageUrl?: string;
195
229
  /**
196
230
  * Whether this item is physically shippable. For external/ad-hoc catalogs
@@ -201,6 +235,11 @@ interface AddLineItemInput {
201
235
  requiresShipping?: boolean;
202
236
  metadata?: Record<string, unknown>;
203
237
  }
238
+ /**
239
+ * On a subscription line only `quantity` (seats) and `metadata` may change;
240
+ * a different `unitPrice`, `taxAmount` or `discountAmount` is
241
+ * 422 `recurring_line_immutable`.
242
+ */
204
243
  interface UpdateLineItemInput {
205
244
  quantity?: number;
206
245
  unitPrice?: number;
@@ -705,4 +744,4 @@ declare function cartErrorFromResponse(args: {
705
744
  responseHeaders?: ThrottleErrorArgs['responseHeaders'];
706
745
  }): ThrottleApiError;
707
746
 
708
- export { type AddLineItemInput, type AppliedDiscount, type Cart, type CartAddress, type CartBranding, CartClient, type CartEvent, CartNotFoundError, CartNotOpenError, type CartSession, type CartSessionCart, CartSessionClient, type CartSessionClientOptions, type CartSessionSummary, CartTimeoutError, type CheckoutHandoff, type CheckoutHandoffInput, type CheckoutInput, type CreateCartInput, type CreateCartSessionInput, type ExternalShippingTaxSnapshotInput, type LineItem, type Order, type SelectShippingInput, type SelectedShipping, type ShippingTaxAddress, type ShippingTaxCalculationResponse, type ShippingTaxCartCalculateInput, type ShippingTaxMethod, type ShippingTaxQuoteInput, type ShippingTaxQuoteItem, StorefrontQuoteClient, type TaxLine, type TaxLineInput, ThrottleApiError, type UpdateCartInput, type UpdateCartSessionInput, type UpdateLineItemInput, cartErrorFromResponse };
747
+ export { type AddLineItemInput, type AppliedDiscount, type Cart, type CartAddress, type CartBranding, CartClient, type CartEvent, CartNotFoundError, CartNotOpenError, type CartSession, type CartSessionCart, CartSessionClient, type CartSessionClientOptions, type CartSessionSummary, CartTimeoutError, type CheckoutHandoff, type CheckoutHandoffInput, type CheckoutInput, type CreateCartInput, type CreateCartSessionInput, type ExternalShippingTaxSnapshotInput, type LegacyLineRecurring, type LineItem, type LineRecurringInput, type Order, type SelectShippingInput, type SelectedShipping, type ShippingTaxAddress, type ShippingTaxCalculationResponse, type ShippingTaxCartCalculateInput, type ShippingTaxMethod, type ShippingTaxQuoteInput, type ShippingTaxQuoteItem, type StoredLineRecurring, StorefrontQuoteClient, type SubscriptionInterval, type SubscriptionIntervalUnit, type TaxLine, type TaxLineInput, ThrottleApiError, type UpdateCartInput, type UpdateCartSessionInput, type UpdateLineItemInput, cartErrorFromResponse };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usethrottle/cart",
3
- "version": "3.10.1",
3
+ "version": "3.11.0",
4
4
  "description": "Typed REST client for the Throttle Cart API.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsup",
33
- "test": "vitest run",
33
+ "test": "tsc --noEmit --strict --skipLibCheck --target es2022 --lib es2022,dom --module esnext --moduleResolution bundler __tests__/recurring-types.check.ts && vitest run",
34
34
  "typecheck": "tsc --noEmit"
35
35
  }
36
36
  }