brainerce 1.52.0 → 1.53.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/dist/index.d.mts CHANGED
@@ -3188,8 +3188,12 @@ interface SetShippingAddressDto {
3188
3188
  * which is the correct behaviour once the two no longer agree.
3189
3189
  *
3190
3190
  * Note there is deliberately no `lat`/`lng` field: zone matching decides
3191
- * which shipping rate is offered and charged, so coordinates are never
3192
- * accepted from the client.
3191
+ * which shipping rate is offered and charged, so the server resolves the
3192
+ * coordinates itself from this `placeId` and never accepts them from the
3193
+ * client. You can still spread `getAddressDetails().address` in here — the
3194
+ * SDK drops its `lat`/`lng`/`formattedAddress` before sending, because the
3195
+ * endpoint rejects any unknown property with a `400` that would otherwise
3196
+ * block checkout entirely.
3193
3197
  */
3194
3198
  placeId?: string;
3195
3199
  /**
@@ -3288,11 +3292,44 @@ interface AddressDetailsResult {
3288
3292
  */
3289
3293
  region: string;
3290
3294
  postalCode: string;
3295
+ /**
3296
+ * ISO-3166-1 alpha-2, or **an empty string** — Google omits the country
3297
+ * outright for places whose sovereignty it declines to attribute, which in
3298
+ * practice includes ordinary residential addresses (verified live: Ramat
3299
+ * Shlomo, Giv'at Ze'ev, Modi'in Ilit, Ma'ale Adumim all come back with no
3300
+ * country). The API deliberately does not guess one.
3301
+ *
3302
+ * Treat it exactly like `region`: when it's empty, leave your country
3303
+ * field for the shopper to confirm rather than submitting a blank
3304
+ * `SetShippingAddressDto.country`. Everything else on the address
3305
+ * (`line1`, `city`, `lat`/`lng`, `formattedAddress`) is still valid and
3306
+ * should still be filled in.
3307
+ *
3308
+ * Note that `region` is necessarily empty too whenever this is — region
3309
+ * codes are resolved *within* a country, so with no country there is no
3310
+ * region list to match against. Prompt for both.
3311
+ */
3291
3312
  country: string;
3313
+ /**
3314
+ * Resolved coordinates, **for your own use only** — a map pin, a distance
3315
+ * readout. They are not part of any address payload: zone matching decides
3316
+ * which shipping rate is offered and charged, so the server re-resolves
3317
+ * them from `placeId` instead of trusting the client. Sending them is
3318
+ * harmless (the SDK strips `lat`/`lng`/`formattedAddress` from
3319
+ * `setShippingAddress()` / `setBillingAddress()` bodies), but pass
3320
+ * `placeId` — that is what actually reaches zone matching.
3321
+ */
3292
3322
  lat: number;
3293
3323
  lng: number;
3294
3324
  formattedAddress: string;
3295
3325
  };
3326
+ /**
3327
+ * Soft coverage hint, never a rejection. Note it can read `false` purely
3328
+ * because `country` came back empty and the store's zones are country-listed
3329
+ * — a polygon zone can still match this address, since the server matches it
3330
+ * against the coordinates it resolves from `placeId`. Show a "we'll confirm
3331
+ * by phone" banner; do not block the shopper.
3332
+ */
3296
3333
  inZone: boolean;
3297
3334
  }
3298
3335
  interface CompleteCheckoutResponse {
@@ -6303,6 +6340,14 @@ declare class BrainerceClient {
6303
6340
  private _pendingRecoverCartId;
6304
6341
  private _ga4MeasurementId;
6305
6342
  private _ga4StitchPromise;
6343
+ /**
6344
+ * Fields present on `getAddressDetails().address` that the address endpoints
6345
+ * do NOT accept — stripped by `stripResolvedOnlyAddressFields()` so a
6346
+ * `{ ...address }` spread doesn't 400 the whole checkout.
6347
+ */
6348
+ private static readonly RESOLVED_ONLY_ADDRESS_FIELDS;
6349
+ /** One warning per client, not per keystroke-driven address submit. */
6350
+ private _warnedResolvedOnlyAddressFields;
6306
6351
  /** localStorage key for session cart reference (sessionToken + cartId) */
6307
6352
  private readonly SESSION_CART_KEY;
6308
6353
  /**
@@ -6376,10 +6421,19 @@ declare class BrainerceClient {
6376
6421
  * Set the customer authentication token (obtained from login/register).
6377
6422
  * Required for accessing customer-specific data in storefront mode.
6378
6423
  *
6424
+ * This is a plain setter — it authenticates subsequent requests and nothing
6425
+ * else. In particular it does NOT attach the shopper's existing guest cart to
6426
+ * their account. Pair every sign-in with {@link syncCartOnLogin}, or the cart
6427
+ * stays anonymous and every feature keyed on buyer identity degrades quietly:
6428
+ * "first order only" discounts keep applying to returning customers,
6429
+ * per-customer usage caps stop being enforced at cart time, and abandoned-cart
6430
+ * recovery can't identify who to email.
6431
+ *
6379
6432
  * @example
6380
6433
  * ```typescript
6381
6434
  * const auth = await client.loginCustomer('user@example.com', 'password');
6382
6435
  * client.setCustomerToken(auth.token);
6436
+ * await client.syncCartOnLogin(); // claim the guest cart for this account
6383
6437
  *
6384
6438
  * // Now can access customer data
6385
6439
  * const profile = await client.getMyProfile();
@@ -6525,6 +6579,24 @@ declare class BrainerceClient {
6525
6579
  * called, or if it hasn't resolved any ids by the time this is awaited.
6526
6580
  */
6527
6581
  private withAnalyticsStitchIds;
6582
+ /**
6583
+ * Drop the fields `getAddressDetails()` returns that no address endpoint
6584
+ * accepts, so spreading its `address` straight into `setShippingAddress()`
6585
+ * / `setBillingAddress()` works instead of failing the whole request.
6586
+ *
6587
+ * The address endpoints validate against a strict allow-list: ONE unknown
6588
+ * property rejects the call with `400 "property lat should not exist"`, and
6589
+ * the shopper cannot check out at all. `lat`/`lng`/`formattedAddress` are
6590
+ * the only realistic way to hit that — they come out of this SDK's own
6591
+ * resolved-address shape, so this SDK cleans up after itself rather than
6592
+ * making every storefront remember to. Nothing else is stripped: a genuine
6593
+ * typo still reaches the server and still fails loudly.
6594
+ *
6595
+ * Coordinates are dropped rather than forwarded because zone matching picks
6596
+ * which shipping rate is offered and charged — the server resolves them
6597
+ * itself from `placeId`, and never takes them from the caller.
6598
+ */
6599
+ private stripResolvedOnlyAddressFields;
6528
6600
  /**
6529
6601
  * Get a list of products with pagination and filtering
6530
6602
  * Works in vibe-coded, storefront (public), and admin mode
@@ -7643,6 +7715,12 @@ declare class BrainerceClient {
7643
7715
  * if (params.get('oauth_success') === 'true' && params.get('auth_code')) {
7644
7716
  * const result = await client.exchangeOAuthCode(params.get('auth_code')!);
7645
7717
  * client.setCustomerToken(result.token);
7718
+ * // REQUIRED: setCustomerToken only stores the JWT — it does NOT attach the
7719
+ * // guest cart to the account. Without this call the cart stays anonymous,
7720
+ * // and anything keyed on the buyer's identity misbehaves: "first order
7721
+ * // only" discounts re-apply to returning customers, per-customer usage
7722
+ * // caps go unenforced, and abandoned-cart recovery can't reach them.
7723
+ * await client.syncCartOnLogin();
7646
7724
  * // result.customer, result.isNewCustomer, result.redirectUrl, ...
7647
7725
  * } else if (params.get('oauth_error')) {
7648
7726
  * // Failures land on this same page, on `redirectUrl` — never on the API
@@ -7677,6 +7755,11 @@ declare class BrainerceClient {
7677
7755
  *
7678
7756
  * @param authCode - The single-use code from the `?auth_code=` URL param.
7679
7757
  *
7758
+ * Always follow a successful exchange with `syncCartOnLogin()`. Storing the
7759
+ * token does not claim the guest cart, and an unclaimed cart has no buyer
7760
+ * identity — which silently breaks first-order discounts, per-customer usage
7761
+ * caps, and abandoned-cart recovery for everyone who signs in with OAuth.
7762
+ *
7680
7763
  * @example
7681
7764
  * ```typescript
7682
7765
  * const params = new URLSearchParams(window.location.search);
@@ -7685,6 +7768,7 @@ declare class BrainerceClient {
7685
7768
  * const { token, customer, isNewCustomer, redirectUrl } =
7686
7769
  * await client.exchangeOAuthCode(code);
7687
7770
  * client.setCustomerToken(token);
7771
+ * await client.syncCartOnLogin(); // attach the guest cart to the account
7688
7772
  * }
7689
7773
  * ```
7690
7774
  */
@@ -8840,6 +8924,11 @@ declare class BrainerceClient {
8840
8924
  * address text — which is materially less accurate and can place the
8841
8925
  * shopper in a neighbouring city's zone, or in none at all.
8842
8926
  *
8927
+ * Spreading `getAddressDetails().address` in here is safe: its `lat`, `lng`
8928
+ * and `formattedAddress` are dropped before the request goes out (the
8929
+ * endpoint rejects unknown properties outright, and coordinates are never
8930
+ * taken from the client — the server resolves them from `placeId`).
8931
+ *
8843
8932
  * @example
8844
8933
  * ```typescript
8845
8934
  * const { checkout, rates } = await client.setShippingAddress('checkout_123', {
@@ -11037,7 +11126,7 @@ declare class BrainerceError extends Error {
11037
11126
  constructor(message: string, statusCode: number, details?: unknown);
11038
11127
  }
11039
11128
 
11040
- declare const SDK_VERSION = "1.52.0";
11129
+ declare const SDK_VERSION = "1.53.0";
11041
11130
 
11042
11131
  /**
11043
11132
  * Verify a webhook signature from Brainerce
package/dist/index.d.ts CHANGED
@@ -3188,8 +3188,12 @@ interface SetShippingAddressDto {
3188
3188
  * which is the correct behaviour once the two no longer agree.
3189
3189
  *
3190
3190
  * Note there is deliberately no `lat`/`lng` field: zone matching decides
3191
- * which shipping rate is offered and charged, so coordinates are never
3192
- * accepted from the client.
3191
+ * which shipping rate is offered and charged, so the server resolves the
3192
+ * coordinates itself from this `placeId` and never accepts them from the
3193
+ * client. You can still spread `getAddressDetails().address` in here — the
3194
+ * SDK drops its `lat`/`lng`/`formattedAddress` before sending, because the
3195
+ * endpoint rejects any unknown property with a `400` that would otherwise
3196
+ * block checkout entirely.
3193
3197
  */
3194
3198
  placeId?: string;
3195
3199
  /**
@@ -3288,11 +3292,44 @@ interface AddressDetailsResult {
3288
3292
  */
3289
3293
  region: string;
3290
3294
  postalCode: string;
3295
+ /**
3296
+ * ISO-3166-1 alpha-2, or **an empty string** — Google omits the country
3297
+ * outright for places whose sovereignty it declines to attribute, which in
3298
+ * practice includes ordinary residential addresses (verified live: Ramat
3299
+ * Shlomo, Giv'at Ze'ev, Modi'in Ilit, Ma'ale Adumim all come back with no
3300
+ * country). The API deliberately does not guess one.
3301
+ *
3302
+ * Treat it exactly like `region`: when it's empty, leave your country
3303
+ * field for the shopper to confirm rather than submitting a blank
3304
+ * `SetShippingAddressDto.country`. Everything else on the address
3305
+ * (`line1`, `city`, `lat`/`lng`, `formattedAddress`) is still valid and
3306
+ * should still be filled in.
3307
+ *
3308
+ * Note that `region` is necessarily empty too whenever this is — region
3309
+ * codes are resolved *within* a country, so with no country there is no
3310
+ * region list to match against. Prompt for both.
3311
+ */
3291
3312
  country: string;
3313
+ /**
3314
+ * Resolved coordinates, **for your own use only** — a map pin, a distance
3315
+ * readout. They are not part of any address payload: zone matching decides
3316
+ * which shipping rate is offered and charged, so the server re-resolves
3317
+ * them from `placeId` instead of trusting the client. Sending them is
3318
+ * harmless (the SDK strips `lat`/`lng`/`formattedAddress` from
3319
+ * `setShippingAddress()` / `setBillingAddress()` bodies), but pass
3320
+ * `placeId` — that is what actually reaches zone matching.
3321
+ */
3292
3322
  lat: number;
3293
3323
  lng: number;
3294
3324
  formattedAddress: string;
3295
3325
  };
3326
+ /**
3327
+ * Soft coverage hint, never a rejection. Note it can read `false` purely
3328
+ * because `country` came back empty and the store's zones are country-listed
3329
+ * — a polygon zone can still match this address, since the server matches it
3330
+ * against the coordinates it resolves from `placeId`. Show a "we'll confirm
3331
+ * by phone" banner; do not block the shopper.
3332
+ */
3296
3333
  inZone: boolean;
3297
3334
  }
3298
3335
  interface CompleteCheckoutResponse {
@@ -6303,6 +6340,14 @@ declare class BrainerceClient {
6303
6340
  private _pendingRecoverCartId;
6304
6341
  private _ga4MeasurementId;
6305
6342
  private _ga4StitchPromise;
6343
+ /**
6344
+ * Fields present on `getAddressDetails().address` that the address endpoints
6345
+ * do NOT accept — stripped by `stripResolvedOnlyAddressFields()` so a
6346
+ * `{ ...address }` spread doesn't 400 the whole checkout.
6347
+ */
6348
+ private static readonly RESOLVED_ONLY_ADDRESS_FIELDS;
6349
+ /** One warning per client, not per keystroke-driven address submit. */
6350
+ private _warnedResolvedOnlyAddressFields;
6306
6351
  /** localStorage key for session cart reference (sessionToken + cartId) */
6307
6352
  private readonly SESSION_CART_KEY;
6308
6353
  /**
@@ -6376,10 +6421,19 @@ declare class BrainerceClient {
6376
6421
  * Set the customer authentication token (obtained from login/register).
6377
6422
  * Required for accessing customer-specific data in storefront mode.
6378
6423
  *
6424
+ * This is a plain setter — it authenticates subsequent requests and nothing
6425
+ * else. In particular it does NOT attach the shopper's existing guest cart to
6426
+ * their account. Pair every sign-in with {@link syncCartOnLogin}, or the cart
6427
+ * stays anonymous and every feature keyed on buyer identity degrades quietly:
6428
+ * "first order only" discounts keep applying to returning customers,
6429
+ * per-customer usage caps stop being enforced at cart time, and abandoned-cart
6430
+ * recovery can't identify who to email.
6431
+ *
6379
6432
  * @example
6380
6433
  * ```typescript
6381
6434
  * const auth = await client.loginCustomer('user@example.com', 'password');
6382
6435
  * client.setCustomerToken(auth.token);
6436
+ * await client.syncCartOnLogin(); // claim the guest cart for this account
6383
6437
  *
6384
6438
  * // Now can access customer data
6385
6439
  * const profile = await client.getMyProfile();
@@ -6525,6 +6579,24 @@ declare class BrainerceClient {
6525
6579
  * called, or if it hasn't resolved any ids by the time this is awaited.
6526
6580
  */
6527
6581
  private withAnalyticsStitchIds;
6582
+ /**
6583
+ * Drop the fields `getAddressDetails()` returns that no address endpoint
6584
+ * accepts, so spreading its `address` straight into `setShippingAddress()`
6585
+ * / `setBillingAddress()` works instead of failing the whole request.
6586
+ *
6587
+ * The address endpoints validate against a strict allow-list: ONE unknown
6588
+ * property rejects the call with `400 "property lat should not exist"`, and
6589
+ * the shopper cannot check out at all. `lat`/`lng`/`formattedAddress` are
6590
+ * the only realistic way to hit that — they come out of this SDK's own
6591
+ * resolved-address shape, so this SDK cleans up after itself rather than
6592
+ * making every storefront remember to. Nothing else is stripped: a genuine
6593
+ * typo still reaches the server and still fails loudly.
6594
+ *
6595
+ * Coordinates are dropped rather than forwarded because zone matching picks
6596
+ * which shipping rate is offered and charged — the server resolves them
6597
+ * itself from `placeId`, and never takes them from the caller.
6598
+ */
6599
+ private stripResolvedOnlyAddressFields;
6528
6600
  /**
6529
6601
  * Get a list of products with pagination and filtering
6530
6602
  * Works in vibe-coded, storefront (public), and admin mode
@@ -7643,6 +7715,12 @@ declare class BrainerceClient {
7643
7715
  * if (params.get('oauth_success') === 'true' && params.get('auth_code')) {
7644
7716
  * const result = await client.exchangeOAuthCode(params.get('auth_code')!);
7645
7717
  * client.setCustomerToken(result.token);
7718
+ * // REQUIRED: setCustomerToken only stores the JWT — it does NOT attach the
7719
+ * // guest cart to the account. Without this call the cart stays anonymous,
7720
+ * // and anything keyed on the buyer's identity misbehaves: "first order
7721
+ * // only" discounts re-apply to returning customers, per-customer usage
7722
+ * // caps go unenforced, and abandoned-cart recovery can't reach them.
7723
+ * await client.syncCartOnLogin();
7646
7724
  * // result.customer, result.isNewCustomer, result.redirectUrl, ...
7647
7725
  * } else if (params.get('oauth_error')) {
7648
7726
  * // Failures land on this same page, on `redirectUrl` — never on the API
@@ -7677,6 +7755,11 @@ declare class BrainerceClient {
7677
7755
  *
7678
7756
  * @param authCode - The single-use code from the `?auth_code=` URL param.
7679
7757
  *
7758
+ * Always follow a successful exchange with `syncCartOnLogin()`. Storing the
7759
+ * token does not claim the guest cart, and an unclaimed cart has no buyer
7760
+ * identity — which silently breaks first-order discounts, per-customer usage
7761
+ * caps, and abandoned-cart recovery for everyone who signs in with OAuth.
7762
+ *
7680
7763
  * @example
7681
7764
  * ```typescript
7682
7765
  * const params = new URLSearchParams(window.location.search);
@@ -7685,6 +7768,7 @@ declare class BrainerceClient {
7685
7768
  * const { token, customer, isNewCustomer, redirectUrl } =
7686
7769
  * await client.exchangeOAuthCode(code);
7687
7770
  * client.setCustomerToken(token);
7771
+ * await client.syncCartOnLogin(); // attach the guest cart to the account
7688
7772
  * }
7689
7773
  * ```
7690
7774
  */
@@ -8840,6 +8924,11 @@ declare class BrainerceClient {
8840
8924
  * address text — which is materially less accurate and can place the
8841
8925
  * shopper in a neighbouring city's zone, or in none at all.
8842
8926
  *
8927
+ * Spreading `getAddressDetails().address` in here is safe: its `lat`, `lng`
8928
+ * and `formattedAddress` are dropped before the request goes out (the
8929
+ * endpoint rejects unknown properties outright, and coordinates are never
8930
+ * taken from the client — the server resolves them from `placeId`).
8931
+ *
8843
8932
  * @example
8844
8933
  * ```typescript
8845
8934
  * const { checkout, rates } = await client.setShippingAddress('checkout_123', {
@@ -11037,7 +11126,7 @@ declare class BrainerceError extends Error {
11037
11126
  constructor(message: string, statusCode: number, details?: unknown);
11038
11127
  }
11039
11128
 
11040
- declare const SDK_VERSION = "1.52.0";
11129
+ declare const SDK_VERSION = "1.53.0";
11041
11130
 
11042
11131
  /**
11043
11132
  * Verify a webhook signature from Brainerce
package/dist/index.js CHANGED
@@ -201,7 +201,7 @@ function isDevGuardsEnabled() {
201
201
  }
202
202
 
203
203
  // src/version.ts
204
- var SDK_VERSION = "1.52.0";
204
+ var SDK_VERSION = "1.53.0";
205
205
 
206
206
  // src/client.ts
207
207
  var DEFAULT_BASE_URL = "https://api.brainerce.com";
@@ -241,7 +241,7 @@ function parseRetryAfterMs(response) {
241
241
  function sleep(ms) {
242
242
  return new Promise((resolve) => setTimeout(resolve, ms));
243
243
  }
244
- var BrainerceClient = class {
244
+ var _BrainerceClient = class _BrainerceClient {
245
245
  constructor(options) {
246
246
  this.customerToken = null;
247
247
  this.customerCartId = null;
@@ -260,6 +260,8 @@ var BrainerceClient = class {
260
260
  // GA4 stitch state (see `loadGoogleAnalytics()` in the Analytics section).
261
261
  this._ga4MeasurementId = null;
262
262
  this._ga4StitchPromise = null;
263
+ /** One warning per client, not per keystroke-driven address submit. */
264
+ this._warnedResolvedOnlyAddressFields = false;
263
265
  /** localStorage key for session cart reference (sessionToken + cartId) */
264
266
  this.SESSION_CART_KEY = "brainerce_session";
265
267
  /**
@@ -773,10 +775,19 @@ var BrainerceClient = class {
773
775
  * Set the customer authentication token (obtained from login/register).
774
776
  * Required for accessing customer-specific data in storefront mode.
775
777
  *
778
+ * This is a plain setter — it authenticates subsequent requests and nothing
779
+ * else. In particular it does NOT attach the shopper's existing guest cart to
780
+ * their account. Pair every sign-in with {@link syncCartOnLogin}, or the cart
781
+ * stays anonymous and every feature keyed on buyer identity degrades quietly:
782
+ * "first order only" discounts keep applying to returning customers,
783
+ * per-customer usage caps stop being enforced at cart time, and abandoned-cart
784
+ * recovery can't identify who to email.
785
+ *
776
786
  * @example
777
787
  * ```typescript
778
788
  * const auth = await client.loginCustomer('user@example.com', 'password');
779
789
  * client.setCustomerToken(auth.token);
790
+ * await client.syncCartOnLogin(); // claim the guest cart for this account
780
791
  *
781
792
  * // Now can access customer data
782
793
  * const profile = await client.getMyProfile();
@@ -1290,6 +1301,39 @@ var BrainerceClient = class {
1290
1301
  return dto;
1291
1302
  }
1292
1303
  }
1304
+ /**
1305
+ * Drop the fields `getAddressDetails()` returns that no address endpoint
1306
+ * accepts, so spreading its `address` straight into `setShippingAddress()`
1307
+ * / `setBillingAddress()` works instead of failing the whole request.
1308
+ *
1309
+ * The address endpoints validate against a strict allow-list: ONE unknown
1310
+ * property rejects the call with `400 "property lat should not exist"`, and
1311
+ * the shopper cannot check out at all. `lat`/`lng`/`formattedAddress` are
1312
+ * the only realistic way to hit that — they come out of this SDK's own
1313
+ * resolved-address shape, so this SDK cleans up after itself rather than
1314
+ * making every storefront remember to. Nothing else is stripped: a genuine
1315
+ * typo still reaches the server and still fails loudly.
1316
+ *
1317
+ * Coordinates are dropped rather than forwarded because zone matching picks
1318
+ * which shipping rate is offered and charged — the server resolves them
1319
+ * itself from `placeId`, and never takes them from the caller.
1320
+ */
1321
+ stripResolvedOnlyAddressFields(address) {
1322
+ if (!address || typeof address !== "object") return address;
1323
+ const present = _BrainerceClient.RESOLVED_ONLY_ADDRESS_FIELDS.filter(
1324
+ (field) => field in address
1325
+ );
1326
+ if (present.length === 0) return address;
1327
+ const cleaned = { ...address };
1328
+ for (const field of present) delete cleaned[field];
1329
+ if (!this._warnedResolvedOnlyAddressFields) {
1330
+ this._warnedResolvedOnlyAddressFields = true;
1331
+ console.warn(
1332
+ `BrainerceClient: dropped ${present.join("/")} from the address payload \u2014 the API does not accept coordinates from the client. Pass \`placeId\` (and \`placeSessionToken\`) instead so the server resolves them itself and matches map-drawn delivery zones against the exact location.`
1333
+ );
1334
+ }
1335
+ return cleaned;
1336
+ }
1293
1337
  // -------------------- Products --------------------
1294
1338
  /**
1295
1339
  * Get a list of products with pagination and filtering
@@ -2882,6 +2926,12 @@ var BrainerceClient = class {
2882
2926
  * if (params.get('oauth_success') === 'true' && params.get('auth_code')) {
2883
2927
  * const result = await client.exchangeOAuthCode(params.get('auth_code')!);
2884
2928
  * client.setCustomerToken(result.token);
2929
+ * // REQUIRED: setCustomerToken only stores the JWT — it does NOT attach the
2930
+ * // guest cart to the account. Without this call the cart stays anonymous,
2931
+ * // and anything keyed on the buyer's identity misbehaves: "first order
2932
+ * // only" discounts re-apply to returning customers, per-customer usage
2933
+ * // caps go unenforced, and abandoned-cart recovery can't reach them.
2934
+ * await client.syncCartOnLogin();
2885
2935
  * // result.customer, result.isNewCustomer, result.redirectUrl, ...
2886
2936
  * } else if (params.get('oauth_error')) {
2887
2937
  * // Failures land on this same page, on `redirectUrl` — never on the API
@@ -2934,6 +2984,11 @@ var BrainerceClient = class {
2934
2984
  *
2935
2985
  * @param authCode - The single-use code from the `?auth_code=` URL param.
2936
2986
  *
2987
+ * Always follow a successful exchange with `syncCartOnLogin()`. Storing the
2988
+ * token does not claim the guest cart, and an unclaimed cart has no buyer
2989
+ * identity — which silently breaks first-order discounts, per-customer usage
2990
+ * caps, and abandoned-cart recovery for everyone who signs in with OAuth.
2991
+ *
2937
2992
  * @example
2938
2993
  * ```typescript
2939
2994
  * const params = new URLSearchParams(window.location.search);
@@ -2942,6 +2997,7 @@ var BrainerceClient = class {
2942
2997
  * const { token, customer, isNewCustomer, redirectUrl } =
2943
2998
  * await client.exchangeOAuthCode(code);
2944
2999
  * client.setCustomerToken(token);
3000
+ * await client.syncCartOnLogin(); // attach the guest cart to the account
2945
3001
  * }
2946
3002
  * ```
2947
3003
  */
@@ -5121,6 +5177,11 @@ var BrainerceClient = class {
5121
5177
  * address text — which is materially less accurate and can place the
5122
5178
  * shopper in a neighbouring city's zone, or in none at all.
5123
5179
  *
5180
+ * Spreading `getAddressDetails().address` in here is safe: its `lat`, `lng`
5181
+ * and `formattedAddress` are dropped before the request goes out (the
5182
+ * endpoint rejects unknown properties outright, and coordinates are never
5183
+ * taken from the client — the server resolves them from `placeId`).
5184
+ *
5124
5185
  * @example
5125
5186
  * ```typescript
5126
5187
  * const { checkout, rates } = await client.setShippingAddress('checkout_123', {
@@ -5140,7 +5201,9 @@ var BrainerceClient = class {
5140
5201
  * ```
5141
5202
  */
5142
5203
  async setShippingAddress(checkoutId, address) {
5143
- const body = await this.withAnalyticsStitchIds(address);
5204
+ const body = await this.withAnalyticsStitchIds(
5205
+ this.stripResolvedOnlyAddressFields(address)
5206
+ );
5144
5207
  if (this.isVibeCodedMode()) {
5145
5208
  return this.vibeCodedRequest(
5146
5209
  "PATCH",
@@ -5453,24 +5516,25 @@ var BrainerceClient = class {
5453
5516
  * ```
5454
5517
  */
5455
5518
  async setBillingAddress(checkoutId, address) {
5519
+ const body = this.stripResolvedOnlyAddressFields(address);
5456
5520
  if (this.isVibeCodedMode()) {
5457
5521
  return this.vibeCodedRequest(
5458
5522
  "PATCH",
5459
5523
  `/checkout/${encodePathSegment(checkoutId)}/billing-address`,
5460
- address
5524
+ body
5461
5525
  );
5462
5526
  }
5463
5527
  if (this.storeId && !this.apiKey) {
5464
5528
  return this.storefrontRequest(
5465
5529
  "PATCH",
5466
5530
  `/checkout/${encodePathSegment(checkoutId)}/billing-address`,
5467
- address
5531
+ body
5468
5532
  );
5469
5533
  }
5470
5534
  return this.adminRequest(
5471
5535
  "PATCH",
5472
5536
  `/api/v1/checkout/${encodePathSegment(checkoutId)}/billing-address`,
5473
- address
5537
+ body
5474
5538
  );
5475
5539
  }
5476
5540
  /**
@@ -6436,7 +6500,7 @@ var BrainerceClient = class {
6436
6500
  const result = await this.vibeCodedRequest(
6437
6501
  "PATCH",
6438
6502
  `/checkout/${encodePathSegment(checkoutId)}/shipping-address`,
6439
- data.shippingAddress
6503
+ this.stripResolvedOnlyAddressFields(data.shippingAddress)
6440
6504
  );
6441
6505
  checkout = result.checkout;
6442
6506
  }
@@ -6444,7 +6508,7 @@ var BrainerceClient = class {
6444
6508
  checkout = await this.vibeCodedRequest(
6445
6509
  "PATCH",
6446
6510
  `/checkout/${encodePathSegment(checkoutId)}/billing-address`,
6447
- data.billingAddress
6511
+ this.stripResolvedOnlyAddressFields(data.billingAddress)
6448
6512
  );
6449
6513
  }
6450
6514
  if (!checkout) {
@@ -9285,6 +9349,17 @@ var BrainerceClient = class {
9285
9349
  );
9286
9350
  }
9287
9351
  };
9352
+ /**
9353
+ * Fields present on `getAddressDetails().address` that the address endpoints
9354
+ * do NOT accept — stripped by `stripResolvedOnlyAddressFields()` so a
9355
+ * `{ ...address }` spread doesn't 400 the whole checkout.
9356
+ */
9357
+ _BrainerceClient.RESOLVED_ONLY_ADDRESS_FIELDS = [
9358
+ "lat",
9359
+ "lng",
9360
+ "formattedAddress"
9361
+ ];
9362
+ var BrainerceClient = _BrainerceClient;
9288
9363
  var BrainerceError = class extends Error {
9289
9364
  constructor(message, statusCode, details) {
9290
9365
  super(message);