@faststore/api 1.8.20 → 1.8.23

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/CHANGELOG.md CHANGED
@@ -3,6 +3,33 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## 1.8.23 (2022-05-17)
7
+
8
+
9
+ ### Features
10
+
11
+ * Sync Cart with Checkout ([#1299](https://github.com/vtex/faststore/issues/1299)) ([62e8f56](https://github.com/vtex/faststore/commit/62e8f56b31c6bb6cf7260749a90ebef8aba9982b))
12
+
13
+
14
+
15
+
16
+
17
+ ## 1.8.22 (2022-05-17)
18
+
19
+ **Note:** Version bump only for package @faststore/api
20
+
21
+
22
+
23
+
24
+
25
+ ## 1.8.21 (2022-05-16)
26
+
27
+ **Note:** Version bump only for package @faststore/api
28
+
29
+
30
+
31
+
32
+
6
33
  ## 1.8.20 (2022-05-16)
7
34
 
8
35
  **Note:** Version bump only for package @faststore/api
@@ -9,6 +9,7 @@ var fetch = _interopDefault(require('isomorphic-unfetch'));
9
9
  var DataLoader = _interopDefault(require('dataloader'));
10
10
  var pLimit = _interopDefault(require('p-limit'));
11
11
  var deepEquals = _interopDefault(require('fast-deep-equal'));
12
+ var crypto = _interopDefault(require('crypto'));
12
13
  var graphql = require('graphql');
13
14
 
14
15
  const fetchAPI = async (info, init) => {
@@ -88,6 +89,19 @@ const VtexCommerce = ({
88
89
  method: 'PATCH'
89
90
  });
90
91
  },
92
+ setCustomData: ({
93
+ id,
94
+ appId,
95
+ key,
96
+ value
97
+ }) => {
98
+ return fetchAPI(`${base}/api/checkout/pub/orderForm/${id}/customData/${appId}/${key}`, { ...BASE_INIT,
99
+ body: JSON.stringify({
100
+ value
101
+ }),
102
+ method: 'PUT'
103
+ });
104
+ },
91
105
  region: async ({
92
106
  postalCode,
93
107
  country,
@@ -616,6 +630,8 @@ const StoreFacetValue = {
616
630
  }) => quantity
617
631
  };
618
632
 
633
+ const md5 = payload => crypto.createHash('md5').update(payload).digest('hex');
634
+
619
635
  const getId = item => [item.itemOffered.sku, item.seller.identifier, item.price].join('::');
620
636
 
621
637
  const orderFormItemToOffer = (item, index) => ({
@@ -662,6 +678,66 @@ const equals = (storeOrder, orderForm) => {
662
678
  const orderItemsAreSync = deepEquals(orderFormItems, storeOrderItems);
663
679
  return isSameOrder && orderItemsAreSync;
664
680
  };
681
+
682
+ const orderFormToCart = (form, skuLoader) => {
683
+ return {
684
+ order: {
685
+ orderNumber: form.orderFormId,
686
+ acceptedOffer: form.items.map(item => ({ ...item,
687
+ product: skuLoader.load([{
688
+ key: 'id',
689
+ value: item.id
690
+ }])
691
+ }))
692
+ },
693
+ messages: form.messages.map(({
694
+ text,
695
+ status
696
+ }) => ({
697
+ text,
698
+ status: status.toUpperCase()
699
+ }))
700
+ };
701
+ };
702
+
703
+ const getOrderFormEtag = ({
704
+ items
705
+ }) => md5(JSON.stringify(items));
706
+
707
+ const setOrderFormEtag = async (form, commerce) => {
708
+ try {
709
+ const orderForm = await commerce.checkout.setCustomData({
710
+ id: form.orderFormId,
711
+ appId: 'faststore',
712
+ key: 'cartEtag',
713
+ value: getOrderFormEtag(form)
714
+ });
715
+ return orderForm;
716
+ } catch (err) {
717
+ console.error('Error while setting custom data to orderForm.\n Make sure to add the following custom app to the orderForm: \n{"fields":["cartEtag"],"id":"faststore","major":1}.\n More info at: https://developers.vtex.com/vtex-rest-api/docs/customizable-fields-with-checkout-api');
718
+ throw err;
719
+ }
720
+ };
721
+ /**
722
+ * Checks if cartEtag stored on customData is up to date
723
+ * @description If cartEtag is not up to date, this means that
724
+ * another system changed the cart, like Checkout UI or Order Placed
725
+ */
726
+
727
+
728
+ const isOrderFormStale = form => {
729
+ var _form$customData, _faststoreData$fields;
730
+
731
+ const faststoreData = (_form$customData = form.customData) == null ? void 0 : _form$customData.customApps.find(app => app.id === 'faststore');
732
+ const oldEtag = faststoreData == null ? void 0 : (_faststoreData$fields = faststoreData.fields) == null ? void 0 : _faststoreData$fields.cartEtag;
733
+
734
+ if (oldEtag == null) {
735
+ return true;
736
+ }
737
+
738
+ const newEtag = getOrderFormEtag(form);
739
+ return newEtag !== oldEtag;
740
+ };
665
741
  /**
666
742
  * This resolver implements the optimistic cart behavior. The main idea in here
667
743
  * is that we receive a cart from the UI (as query params) and we validate it with
@@ -682,6 +758,9 @@ const validateCart = async (_, {
682
758
  order
683
759
  }
684
760
  }, ctx) => {
761
+ const {
762
+ enableOrderFormSync
763
+ } = ctx.storage.flags;
685
764
  const {
686
765
  orderNumber,
687
766
  acceptedOffer
@@ -697,7 +776,19 @@ const validateCart = async (_, {
697
776
 
698
777
  const orderForm = await commerce.checkout.orderForm({
699
778
  id: orderNumber
700
- }); // Step2: Process items from both browser and checkout so they have the same shape
779
+ }); // Step1.5: Check if another system changed the orderForm with this orderNumber
780
+ // If so, this means the user interacted with this cart elsewhere and expects
781
+ // to see this new cart state instead of what's stored on the user's browser.
782
+
783
+ if (enableOrderFormSync === true) {
784
+ const isStale = isOrderFormStale(orderForm);
785
+
786
+ if (isStale === true && orderNumber) {
787
+ const newOrderForm = await setOrderFormEtag(orderForm, commerce);
788
+ return orderFormToCart(newOrderForm, skuLoader);
789
+ }
790
+ } // Step2: Process items from both browser and checkout so they have the same shape
791
+
701
792
 
702
793
  const browserItemsById = groupById(acceptedOffer);
703
794
  const originItemsById = groupById(orderForm.items.map(orderFormItemToOffer));
@@ -735,34 +826,19 @@ const validateCart = async (_, {
735
826
  } // Step4: Apply delta changes to order form
736
827
 
737
828
 
738
- const updatedOrderForm = await commerce.checkout.updateOrderFormItems({
829
+ const updatedOrderForm = await commerce.checkout // update orderForm items
830
+ .updateOrderFormItems({
739
831
  id: orderForm.orderFormId,
740
832
  orderItems: changes
741
- }); // Step5: If no changes detected before/after updating orderForm, the order is validated
833
+ }) // update orderForm etag so we know last time we touched this orderForm
834
+ .then(form => enableOrderFormSync ? setOrderFormEtag(form, commerce) : form); // Step5: If no changes detected before/after updating orderForm, the order is validated
742
835
 
743
836
  if (equals(order, updatedOrderForm)) {
744
837
  return null;
745
- } // Step6: There were changes, convert orderForm to StoreOrder
838
+ } // Step6: There were changes, convert orderForm to StoreCart
746
839
 
747
840
 
748
- return {
749
- order: {
750
- orderNumber: updatedOrderForm.orderFormId,
751
- acceptedOffer: updatedOrderForm.items.map(item => ({ ...item,
752
- product: skuLoader.load([{
753
- key: 'id',
754
- value: item.id
755
- }])
756
- }))
757
- },
758
- messages: updatedOrderForm.messages.map(({
759
- text,
760
- status
761
- }) => ({
762
- text,
763
- status: status.toUpperCase()
764
- }))
765
- };
841
+ return orderFormToCart(updatedOrderForm, skuLoader);
766
842
  };
767
843
 
768
844
  class ChannelMarshal {
@@ -1403,8 +1479,11 @@ const Resolvers = {
1403
1479
  Mutation
1404
1480
  };
1405
1481
  const getContextFactory = options => ctx => {
1482
+ var _options$flags;
1483
+
1406
1484
  ctx.storage = {
1407
- channel: ChannelMarshal.parse(options.channel)
1485
+ channel: ChannelMarshal.parse(options.channel),
1486
+ flags: (_options$flags = options.flags) != null ? _options$flags : {}
1408
1487
  };
1409
1488
  ctx.clients = getClients(options, ctx);
1410
1489
  ctx.loaders = getLoaders(options, ctx);