@bacano/sdk 1.2.7 → 1.2.8

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/index.js CHANGED
@@ -3277,6 +3277,78 @@ function hasRecoverableCartSessionError(errors) {
3277
3277
  );
3278
3278
  }
3279
3279
 
3280
+ // libs/sdk/src/modules/checkout-contact.ts
3281
+ var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3282
+ var PHONE_PATTERN = /^\+?\d{7,15}$/;
3283
+ function validEmail(value) {
3284
+ if (value.length > 320 || !EMAIL_PATTERN.test(value)) return false;
3285
+ const [local, domain] = value.split("@");
3286
+ if (!local || !domain || local.length > 64 || domain.length > 255 || local.startsWith(".") || local.endsWith(".") || local.includes("..")) {
3287
+ return false;
3288
+ }
3289
+ return domain.split(".").every((label) => /^[a-z\d](?:[a-z\d-]*[a-z\d])?$/i.test(label));
3290
+ }
3291
+ function normalizePhone(value) {
3292
+ return value.trim().replace(/[\s().-]/g, "");
3293
+ }
3294
+ function invalidContact(field) {
3295
+ const messages = {
3296
+ email: "Revisa el correo electr\xF3nico: debe estar completo y sin espacios.",
3297
+ phone: "Revisa el n\xFAmero de tel\xE9fono: usa entre 7 y 15 d\xEDgitos.",
3298
+ contact: "Ingresa un correo electr\xF3nico o un n\xFAmero de tel\xE9fono v\xE1lido."
3299
+ };
3300
+ return new WebsiteError(
3301
+ messages[field],
3302
+ `INVALID_CHECKOUT_${field.toUpperCase()}`
3303
+ );
3304
+ }
3305
+ function normalizeCheckoutContact(contact, mode) {
3306
+ const email = contact.email?.trim() || void 0;
3307
+ const phone = contact.phone ? normalizePhone(contact.phone) : void 0;
3308
+ const cellphone = contact.cellphone ? normalizePhone(contact.cellphone) : void 0;
3309
+ const emailValid = !email || validEmail(email);
3310
+ const phoneValid = !phone || PHONE_PATTERN.test(phone);
3311
+ const cellphoneValid = !cellphone || PHONE_PATTERN.test(cellphone);
3312
+ if (mode === "submit") {
3313
+ if (!emailValid) throw invalidContact("email");
3314
+ if (!phoneValid || !cellphoneValid) throw invalidContact("phone");
3315
+ }
3316
+ const normalized = {
3317
+ ...contact,
3318
+ name: contact.name?.trim() || void 0,
3319
+ firstName: contact.firstName?.trim() || void 0,
3320
+ lastName: contact.lastName?.trim() || void 0,
3321
+ email: emailValid ? email : void 0,
3322
+ phone: phoneValid ? phone : void 0,
3323
+ cellphone: cellphoneValid ? cellphone : void 0
3324
+ };
3325
+ if ((normalized.name?.length ?? 0) > 160 || (normalized.firstName?.length ?? 0) > 80 || (normalized.lastName?.length ?? 0) > 80) {
3326
+ throw new WebsiteError(
3327
+ "Revisa los datos de contacto: el nombre es demasiado largo.",
3328
+ "INVALID_CHECKOUT_CONTACT"
3329
+ );
3330
+ }
3331
+ if (!normalized.email && !normalized.phone && !normalized.cellphone) {
3332
+ throw invalidContact(
3333
+ !emailValid ? "email" : !phoneValid || !cellphoneValid ? "phone" : "contact"
3334
+ );
3335
+ }
3336
+ return normalized;
3337
+ }
3338
+ function explainCheckoutRequestError(error) {
3339
+ if (error instanceof GraphQLRequestError && error.graphqlErrors.some(
3340
+ (item) => /invalid request payload|hay un error en los datos del pedido/i.test(
3341
+ item.message
3342
+ )
3343
+ )) {
3344
+ throw new WebsiteError(
3345
+ "Hay un error en los datos del pedido. Rev\xEDsalos e intenta de nuevo.",
3346
+ "INVALID_CHECKOUT_DATA"
3347
+ );
3348
+ }
3349
+ throw error;
3350
+ }
3351
+
3280
3352
  // libs/sdk/src/modules/cart.ts
3281
3353
  function mapCartItem(raw) {
3282
3354
  return {
@@ -3352,6 +3424,15 @@ function requireCartSession(storage) {
3352
3424
  return session;
3353
3425
  }
3354
3426
  function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage) {
3427
+ let pendingCartSessionOperation = Promise.resolve();
3428
+ function enqueueCartSessionOperation(run) {
3429
+ const operation = pendingCartSessionOperation.then(run, run);
3430
+ pendingCartSessionOperation = operation.then(
3431
+ () => void 0,
3432
+ () => void 0
3433
+ );
3434
+ return operation;
3435
+ }
3355
3436
  async function fetchCart(orderId, cartToken) {
3356
3437
  const data = await executeGraphQL(graphqlUrl, GET_CART, { orderId, cartToken }, getHeaders);
3357
3438
  const result = data.websiteGetCart;
@@ -3374,72 +3455,80 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
3374
3455
  }
3375
3456
  return {
3376
3457
  async getOrCreate(contact, fulfillmentFamily) {
3377
- const ctx = getContext();
3378
- if (!ctx.chainId) {
3379
- throw new WebsiteError(
3380
- "This website has no chain configured. Cart requires a chain_id.",
3381
- "NO_CHAIN"
3382
- );
3383
- }
3384
- const existing = cartSessionStorage.get();
3385
- const data = await executeGraphQL(
3386
- graphqlUrl,
3387
- CREATE_CART,
3388
- {
3389
- websiteId: ctx.websiteId,
3390
- cartToken: existing?.cartToken,
3391
- contact,
3392
- fulfillmentFamily
3393
- },
3394
- getHeaders
3395
- );
3396
- const result = data.websiteCreateCart;
3397
- assertActionSuccess(result, "CART_CREATE_ERROR");
3398
- cartSessionStorage.set({
3399
- orderId: result.order_id,
3400
- cartToken: result.cart_token
3401
- });
3402
- return mapCart(result.cart, result.cart_token);
3403
- },
3404
- async startCheckout(input) {
3405
- const ctx = getContext();
3406
- if (!ctx.chainId) {
3407
- throw new WebsiteError(
3408
- "This website has no chain configured. Checkout requires a chain_id.",
3409
- "NO_CHAIN"
3410
- );
3411
- }
3412
- const requestStartCheckout = async (cartToken) => {
3458
+ return enqueueCartSessionOperation(async () => {
3459
+ const normalizedContact = contact ? normalizeCheckoutContact(contact, "draft") : void 0;
3460
+ const ctx = getContext();
3461
+ if (!ctx.chainId) {
3462
+ throw new WebsiteError(
3463
+ "This website has no chain configured. Cart requires a chain_id.",
3464
+ "NO_CHAIN"
3465
+ );
3466
+ }
3467
+ const existing = cartSessionStorage.get();
3413
3468
  const data = await executeGraphQL(
3414
3469
  graphqlUrl,
3415
- START_GUEST_CHECKOUT,
3470
+ CREATE_CART,
3416
3471
  {
3417
3472
  websiteId: ctx.websiteId,
3418
- cartToken,
3419
- contact: input.contact,
3420
- fulfillmentFamily: input.fulfillmentFamily,
3421
- items: input.items.map((item) => ({
3422
- product_variant_id: item.productVariantId,
3423
- quantity: item.quantity
3424
- })),
3425
- marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0
3473
+ cartToken: existing?.cartToken,
3474
+ contact: normalizedContact,
3475
+ fulfillmentFamily
3426
3476
  },
3427
3477
  getHeaders
3428
- );
3429
- return data.websiteStartGuestCheckout;
3430
- };
3431
- const existing = cartSessionStorage.get();
3432
- let result = await requestStartCheckout(existing?.cartToken);
3433
- if (existing && !result.success && hasRecoverableCartSessionError(result.errors)) {
3434
- cartSessionStorage.clear();
3435
- result = await requestStartCheckout();
3436
- }
3437
- assertActionSuccess(result, "CHECKOUT_START_ERROR");
3438
- cartSessionStorage.set({
3439
- orderId: result.order_id,
3440
- cartToken: result.cart_token
3478
+ ).catch(explainCheckoutRequestError);
3479
+ const result = data.websiteCreateCart;
3480
+ assertActionSuccess(result, "CART_CREATE_ERROR");
3481
+ cartSessionStorage.set({
3482
+ orderId: result.order_id,
3483
+ cartToken: result.cart_token
3484
+ });
3485
+ return mapCart(result.cart, result.cart_token);
3486
+ });
3487
+ },
3488
+ async startCheckout(input) {
3489
+ return enqueueCartSessionOperation(async () => {
3490
+ const contact = normalizeCheckoutContact(input.contact, "draft");
3491
+ const ctx = getContext();
3492
+ if (!ctx.chainId) {
3493
+ throw new WebsiteError(
3494
+ "This website has no chain configured. Checkout requires a chain_id.",
3495
+ "NO_CHAIN"
3496
+ );
3497
+ }
3498
+ const requestStartCheckout = async (cartToken) => {
3499
+ const data = await executeGraphQL(
3500
+ graphqlUrl,
3501
+ START_GUEST_CHECKOUT,
3502
+ {
3503
+ websiteId: ctx.websiteId,
3504
+ cartToken,
3505
+ contact,
3506
+ fulfillmentFamily: input.fulfillmentFamily,
3507
+ items: input.items.map((item) => ({
3508
+ product_variant_id: item.productVariantId,
3509
+ quantity: item.quantity
3510
+ })),
3511
+ marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(
3512
+ input.marketingAttribution
3513
+ ) : void 0
3514
+ },
3515
+ getHeaders
3516
+ ).catch(explainCheckoutRequestError);
3517
+ return data.websiteStartGuestCheckout;
3518
+ };
3519
+ const existing = cartSessionStorage.get();
3520
+ let result = await requestStartCheckout(existing?.cartToken);
3521
+ if (existing && !result.success && hasRecoverableCartSessionError(result.errors)) {
3522
+ cartSessionStorage.clear();
3523
+ result = await requestStartCheckout();
3524
+ }
3525
+ assertActionSuccess(result, "CHECKOUT_START_ERROR");
3526
+ cartSessionStorage.set({
3527
+ orderId: result.order_id,
3528
+ cartToken: result.cart_token
3529
+ });
3530
+ return mapCart(result.cart, result.cart_token);
3441
3531
  });
3442
- return mapCart(result.cart, result.cart_token);
3443
3532
  },
3444
3533
  async get() {
3445
3534
  const session = cartSessionStorage.get();
@@ -5538,6 +5627,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
5538
5627
  return mapCouponResult(data.websiteRemoveCoupon);
5539
5628
  },
5540
5629
  async submit(input) {
5630
+ const contact = normalizeCheckoutContact(input.contact, "submit");
5541
5631
  const session = requireCartSession2(cartSessionStorage);
5542
5632
  const data = await executeGraphQL(
5543
5633
  graphqlUrl,
@@ -5545,7 +5635,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
5545
5635
  {
5546
5636
  orderId: session.orderId,
5547
5637
  cartToken: session.cartToken,
5548
- contact: input.contact,
5638
+ contact,
5549
5639
  fulfillmentFamily: input.fulfillmentFamily,
5550
5640
  address: input.address,
5551
5641
  shippingQuoteId: input.shippingQuoteId,
@@ -5565,7 +5655,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
5565
5655
  } : void 0
5566
5656
  },
5567
5657
  getHeaders
5568
- );
5658
+ ).catch(explainCheckoutRequestError);
5569
5659
  const result = data.websiteCheckout;
5570
5660
  if (!result.success && result.errors && result.errors.length > 0) {
5571
5661
  throw new CheckoutError(result.errors);
@@ -5606,7 +5696,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
5606
5696
  returnUrl: input.returnUrl
5607
5697
  },
5608
5698
  getHeaders
5609
- );
5699
+ ).catch(explainCheckoutRequestError);
5610
5700
  const result = data.websiteCreatePaymentSession;
5611
5701
  if (result.success && result.payment_session_id && (result.integration_mode !== "redirect" || !result.redirect_url)) {
5612
5702
  throw new WebsiteError(
@@ -5641,7 +5731,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
5641
5731
  gatewayTransactionId: input.gatewayTransactionId
5642
5732
  },
5643
5733
  getHeaders
5644
- );
5734
+ ).catch(explainCheckoutRequestError);
5645
5735
  const result = data.websiteReconcilePaymentSession;
5646
5736
  return {
5647
5737
  success: result.success,
@@ -0,0 +1,4 @@
1
+ import type { WebsiteGuestContact } from "../types";
2
+ /** Drafts can use the valid contact channel while the buyer edits the other one. */
3
+ export declare function normalizeCheckoutContact(contact: WebsiteGuestContact, mode: "draft" | "submit"): WebsiteGuestContact;
4
+ export declare function explainCheckoutRequestError(error: unknown): never;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bacano/sdk",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
4
4
  "description": "TypeScript SDK for building websites on the Bacano ERP",
5
5
  "main": "./index.cjs",
6
6
  "module": "./index.js",
@@ -33,6 +33,11 @@
33
33
  "import": "./content-management.js",
34
34
  "require": "./content-management.cjs"
35
35
  },
36
+ "./catalog-management": {
37
+ "types": "./catalog-management.d.ts",
38
+ "import": "./catalog-management.js",
39
+ "require": "./catalog-management.cjs"
40
+ },
36
41
  "./react": {
37
42
  "types": "./react.d.ts",
38
43
  "import": "./react.js",
package/react.cjs CHANGED
@@ -5930,6 +5930,78 @@ function hasRecoverableCartSessionError(errors) {
5930
5930
  );
5931
5931
  }
5932
5932
 
5933
+ // libs/sdk/src/modules/checkout-contact.ts
5934
+ var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
5935
+ var PHONE_PATTERN = /^\+?\d{7,15}$/;
5936
+ function validEmail(value) {
5937
+ if (value.length > 320 || !EMAIL_PATTERN.test(value)) return false;
5938
+ const [local, domain] = value.split("@");
5939
+ if (!local || !domain || local.length > 64 || domain.length > 255 || local.startsWith(".") || local.endsWith(".") || local.includes("..")) {
5940
+ return false;
5941
+ }
5942
+ return domain.split(".").every((label) => /^[a-z\d](?:[a-z\d-]*[a-z\d])?$/i.test(label));
5943
+ }
5944
+ function normalizePhone(value) {
5945
+ return value.trim().replace(/[\s().-]/g, "");
5946
+ }
5947
+ function invalidContact(field) {
5948
+ const messages = {
5949
+ email: "Revisa el correo electr\xF3nico: debe estar completo y sin espacios.",
5950
+ phone: "Revisa el n\xFAmero de tel\xE9fono: usa entre 7 y 15 d\xEDgitos.",
5951
+ contact: "Ingresa un correo electr\xF3nico o un n\xFAmero de tel\xE9fono v\xE1lido."
5952
+ };
5953
+ return new WebsiteError(
5954
+ messages[field],
5955
+ `INVALID_CHECKOUT_${field.toUpperCase()}`
5956
+ );
5957
+ }
5958
+ function normalizeCheckoutContact(contact, mode) {
5959
+ const email = contact.email?.trim() || void 0;
5960
+ const phone = contact.phone ? normalizePhone(contact.phone) : void 0;
5961
+ const cellphone = contact.cellphone ? normalizePhone(contact.cellphone) : void 0;
5962
+ const emailValid = !email || validEmail(email);
5963
+ const phoneValid = !phone || PHONE_PATTERN.test(phone);
5964
+ const cellphoneValid = !cellphone || PHONE_PATTERN.test(cellphone);
5965
+ if (mode === "submit") {
5966
+ if (!emailValid) throw invalidContact("email");
5967
+ if (!phoneValid || !cellphoneValid) throw invalidContact("phone");
5968
+ }
5969
+ const normalized = {
5970
+ ...contact,
5971
+ name: contact.name?.trim() || void 0,
5972
+ firstName: contact.firstName?.trim() || void 0,
5973
+ lastName: contact.lastName?.trim() || void 0,
5974
+ email: emailValid ? email : void 0,
5975
+ phone: phoneValid ? phone : void 0,
5976
+ cellphone: cellphoneValid ? cellphone : void 0
5977
+ };
5978
+ if ((normalized.name?.length ?? 0) > 160 || (normalized.firstName?.length ?? 0) > 80 || (normalized.lastName?.length ?? 0) > 80) {
5979
+ throw new WebsiteError(
5980
+ "Revisa los datos de contacto: el nombre es demasiado largo.",
5981
+ "INVALID_CHECKOUT_CONTACT"
5982
+ );
5983
+ }
5984
+ if (!normalized.email && !normalized.phone && !normalized.cellphone) {
5985
+ throw invalidContact(
5986
+ !emailValid ? "email" : !phoneValid || !cellphoneValid ? "phone" : "contact"
5987
+ );
5988
+ }
5989
+ return normalized;
5990
+ }
5991
+ function explainCheckoutRequestError(error) {
5992
+ if (error instanceof GraphQLRequestError && error.graphqlErrors.some(
5993
+ (item) => /invalid request payload|hay un error en los datos del pedido/i.test(
5994
+ item.message
5995
+ )
5996
+ )) {
5997
+ throw new WebsiteError(
5998
+ "Hay un error en los datos del pedido. Rev\xEDsalos e intenta de nuevo.",
5999
+ "INVALID_CHECKOUT_DATA"
6000
+ );
6001
+ }
6002
+ throw error;
6003
+ }
6004
+
5933
6005
  // libs/sdk/src/modules/cart.ts
5934
6006
  function mapCartItem(raw) {
5935
6007
  return {
@@ -6005,6 +6077,15 @@ function requireCartSession(storage) {
6005
6077
  return session;
6006
6078
  }
6007
6079
  function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage) {
6080
+ let pendingCartSessionOperation = Promise.resolve();
6081
+ function enqueueCartSessionOperation(run) {
6082
+ const operation = pendingCartSessionOperation.then(run, run);
6083
+ pendingCartSessionOperation = operation.then(
6084
+ () => void 0,
6085
+ () => void 0
6086
+ );
6087
+ return operation;
6088
+ }
6008
6089
  async function fetchCart(orderId, cartToken) {
6009
6090
  const data = await executeGraphQL(graphqlUrl, GET_CART, { orderId, cartToken }, getHeaders);
6010
6091
  const result = data.websiteGetCart;
@@ -6027,72 +6108,80 @@ function createCartModule(graphqlUrl, getHeaders, getContext, cartSessionStorage
6027
6108
  }
6028
6109
  return {
6029
6110
  async getOrCreate(contact, fulfillmentFamily) {
6030
- const ctx = getContext();
6031
- if (!ctx.chainId) {
6032
- throw new WebsiteError(
6033
- "This website has no chain configured. Cart requires a chain_id.",
6034
- "NO_CHAIN"
6035
- );
6036
- }
6037
- const existing = cartSessionStorage.get();
6038
- const data = await executeGraphQL(
6039
- graphqlUrl,
6040
- CREATE_CART,
6041
- {
6042
- websiteId: ctx.websiteId,
6043
- cartToken: existing?.cartToken,
6044
- contact,
6045
- fulfillmentFamily
6046
- },
6047
- getHeaders
6048
- );
6049
- const result = data.websiteCreateCart;
6050
- assertActionSuccess(result, "CART_CREATE_ERROR");
6051
- cartSessionStorage.set({
6052
- orderId: result.order_id,
6053
- cartToken: result.cart_token
6054
- });
6055
- return mapCart(result.cart, result.cart_token);
6056
- },
6057
- async startCheckout(input) {
6058
- const ctx = getContext();
6059
- if (!ctx.chainId) {
6060
- throw new WebsiteError(
6061
- "This website has no chain configured. Checkout requires a chain_id.",
6062
- "NO_CHAIN"
6063
- );
6064
- }
6065
- const requestStartCheckout = async (cartToken) => {
6111
+ return enqueueCartSessionOperation(async () => {
6112
+ const normalizedContact = contact ? normalizeCheckoutContact(contact, "draft") : void 0;
6113
+ const ctx = getContext();
6114
+ if (!ctx.chainId) {
6115
+ throw new WebsiteError(
6116
+ "This website has no chain configured. Cart requires a chain_id.",
6117
+ "NO_CHAIN"
6118
+ );
6119
+ }
6120
+ const existing = cartSessionStorage.get();
6066
6121
  const data = await executeGraphQL(
6067
6122
  graphqlUrl,
6068
- START_GUEST_CHECKOUT,
6123
+ CREATE_CART,
6069
6124
  {
6070
6125
  websiteId: ctx.websiteId,
6071
- cartToken,
6072
- contact: input.contact,
6073
- fulfillmentFamily: input.fulfillmentFamily,
6074
- items: input.items.map((item) => ({
6075
- product_variant_id: item.productVariantId,
6076
- quantity: item.quantity
6077
- })),
6078
- marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(input.marketingAttribution) : void 0
6126
+ cartToken: existing?.cartToken,
6127
+ contact: normalizedContact,
6128
+ fulfillmentFamily
6079
6129
  },
6080
6130
  getHeaders
6081
- );
6082
- return data.websiteStartGuestCheckout;
6083
- };
6084
- const existing = cartSessionStorage.get();
6085
- let result = await requestStartCheckout(existing?.cartToken);
6086
- if (existing && !result.success && hasRecoverableCartSessionError(result.errors)) {
6087
- cartSessionStorage.clear();
6088
- result = await requestStartCheckout();
6089
- }
6090
- assertActionSuccess(result, "CHECKOUT_START_ERROR");
6091
- cartSessionStorage.set({
6092
- orderId: result.order_id,
6093
- cartToken: result.cart_token
6131
+ ).catch(explainCheckoutRequestError);
6132
+ const result = data.websiteCreateCart;
6133
+ assertActionSuccess(result, "CART_CREATE_ERROR");
6134
+ cartSessionStorage.set({
6135
+ orderId: result.order_id,
6136
+ cartToken: result.cart_token
6137
+ });
6138
+ return mapCart(result.cart, result.cart_token);
6139
+ });
6140
+ },
6141
+ async startCheckout(input) {
6142
+ return enqueueCartSessionOperation(async () => {
6143
+ const contact = normalizeCheckoutContact(input.contact, "draft");
6144
+ const ctx = getContext();
6145
+ if (!ctx.chainId) {
6146
+ throw new WebsiteError(
6147
+ "This website has no chain configured. Checkout requires a chain_id.",
6148
+ "NO_CHAIN"
6149
+ );
6150
+ }
6151
+ const requestStartCheckout = async (cartToken) => {
6152
+ const data = await executeGraphQL(
6153
+ graphqlUrl,
6154
+ START_GUEST_CHECKOUT,
6155
+ {
6156
+ websiteId: ctx.websiteId,
6157
+ cartToken,
6158
+ contact,
6159
+ fulfillmentFamily: input.fulfillmentFamily,
6160
+ items: input.items.map((item) => ({
6161
+ product_variant_id: item.productVariantId,
6162
+ quantity: item.quantity
6163
+ })),
6164
+ marketingAttribution: input.marketingAttribution ? serializeWebsiteMarketingAttribution(
6165
+ input.marketingAttribution
6166
+ ) : void 0
6167
+ },
6168
+ getHeaders
6169
+ ).catch(explainCheckoutRequestError);
6170
+ return data.websiteStartGuestCheckout;
6171
+ };
6172
+ const existing = cartSessionStorage.get();
6173
+ let result = await requestStartCheckout(existing?.cartToken);
6174
+ if (existing && !result.success && hasRecoverableCartSessionError(result.errors)) {
6175
+ cartSessionStorage.clear();
6176
+ result = await requestStartCheckout();
6177
+ }
6178
+ assertActionSuccess(result, "CHECKOUT_START_ERROR");
6179
+ cartSessionStorage.set({
6180
+ orderId: result.order_id,
6181
+ cartToken: result.cart_token
6182
+ });
6183
+ return mapCart(result.cart, result.cart_token);
6094
6184
  });
6095
- return mapCart(result.cart, result.cart_token);
6096
6185
  },
6097
6186
  async get() {
6098
6187
  const session = cartSessionStorage.get();
@@ -6631,6 +6720,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
6631
6720
  return mapCouponResult(data.websiteRemoveCoupon);
6632
6721
  },
6633
6722
  async submit(input) {
6723
+ const contact = normalizeCheckoutContact(input.contact, "submit");
6634
6724
  const session = requireCartSession2(cartSessionStorage);
6635
6725
  const data = await executeGraphQL(
6636
6726
  graphqlUrl,
@@ -6638,7 +6728,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
6638
6728
  {
6639
6729
  orderId: session.orderId,
6640
6730
  cartToken: session.cartToken,
6641
- contact: input.contact,
6731
+ contact,
6642
6732
  fulfillmentFamily: input.fulfillmentFamily,
6643
6733
  address: input.address,
6644
6734
  shippingQuoteId: input.shippingQuoteId,
@@ -6658,7 +6748,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
6658
6748
  } : void 0
6659
6749
  },
6660
6750
  getHeaders
6661
- );
6751
+ ).catch(explainCheckoutRequestError);
6662
6752
  const result = data.websiteCheckout;
6663
6753
  if (!result.success && result.errors && result.errors.length > 0) {
6664
6754
  throw new CheckoutError(result.errors);
@@ -6699,7 +6789,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
6699
6789
  returnUrl: input.returnUrl
6700
6790
  },
6701
6791
  getHeaders
6702
- );
6792
+ ).catch(explainCheckoutRequestError);
6703
6793
  const result = data.websiteCreatePaymentSession;
6704
6794
  if (result.success && result.payment_session_id && (result.integration_mode !== "redirect" || !result.redirect_url)) {
6705
6795
  throw new WebsiteError(
@@ -6734,7 +6824,7 @@ function createCheckoutModule(graphqlUrl, getHeaders, getContext, cartSessionSto
6734
6824
  gatewayTransactionId: input.gatewayTransactionId
6735
6825
  },
6736
6826
  getHeaders
6737
- );
6827
+ ).catch(explainCheckoutRequestError);
6738
6828
  const result = data.websiteReconcilePaymentSession;
6739
6829
  return {
6740
6830
  success: result.success,