From bd12edf1550630f7962f5a5a1cd221d9bec36f75 Mon Sep 17 00:00:00 2001 From: guitavano Date: Wed, 23 Oct 2024 08:11:22 -0300 Subject: [PATCH] the big one commit --- .DS_Store | Bin 0 -> 6148 bytes .github/issue_template.md | 45 + .github/pull_request_template.md | 18 + .github/release.yaml | 20 + .github/workflows/ci.yaml | 54 + .github/workflows/issues.yaml | 18 + .github/workflows/release.yaml | 45 + .github/workflows/releaser.yaml | 172 + .vscode/settings.json | 2 +- CODE-OF-CONDUCT.md | 74 + CONTRIBUTING.md | 88 + LICENSE | 204 + MAINTAINERS.txt | 3 + README.md | 94 + admin/types.ts | 41 + admin/widgets.ts | 71 + ai-assistants/actions/awsUploadImage.ts | 66 + ai-assistants/actions/chat.ts | 178 + ai-assistants/actions/describeImage.ts | 91 + ai-assistants/actions/transcribeAudio.ts | 60 + ai-assistants/chat/messages.ts | 319 + ai-assistants/deps.ts | 18 + ai-assistants/hooks/useFileUpload.ts | 9 + ai-assistants/loaders/messages.ts | 90 + ai-assistants/logo.png | Bin 0 -> 2692 bytes ai-assistants/manifest.gen.ts | 27 + ai-assistants/mod.ts | 207 + ai-assistants/runtime.ts | 3 + ai-assistants/schema.ts | 32 + ai-assistants/types.ts | 38 + ai-assistants/utils/blobConversion.ts | 51 + algolia/README.md | 44 + algolia/actions/index/product.ts | 43 + algolia/actions/index/wait.ts | 16 + algolia/actions/setup.ts | 23 + algolia/loaders/client.ts | 14 + algolia/loaders/product/list.ts | 63 + algolia/loaders/product/listingPage.ts | 280 + algolia/loaders/product/suggestions.ts | 106 + algolia/logo.png | Bin 0 -> 10097 bytes algolia/manifest.gen.ts | 39 + algolia/mod.ts | 88 + algolia/sections/Analytics/Algolia.tsx | 179 + algolia/utils/highlight.ts | 41 + algolia/utils/product.ts | 349 + algolia/workflows/index/product.ts | 41 + analytics/components/DecoAnalytics.tsx | 92 + analytics/loaders/DecoAnalyticsScript.ts | 86 + analytics/logo.png | Bin 0 -> 1920 bytes analytics/manifest.gen.ts | 21 + analytics/mod.ts | 30 + analytics/scripts/plausible_scripts.ts | 24 + .../sections/Analytics/DecoAnalytics.tsx | 14 + anthropic/actions/code.ts | 62 + anthropic/actions/invoke.ts | 77 + anthropic/actions/stream.ts | 89 + anthropic/deps.ts | 1 + anthropic/manifest.gen.ts | 21 + anthropic/mod.ts | 35 + anthropic/utils.ts | 92 + assets/Konfidency.svg | 9 + assets/logo.svg | 210 + blog/loaders/Author.ts | 9 + blog/loaders/BlogPostItem.ts | 36 + blog/loaders/BlogPostPage.ts | 53 + blog/loaders/Blogpost.ts | 9 + blog/loaders/BlogpostList.ts | 75 + blog/loaders/BlogpostListing.ts | 119 + blog/loaders/Category.ts | 9 + blog/loaders/GetCategories.ts | 69 + blog/manifest.gen.ts | 39 + blog/mod.ts | 29 + blog/sections/Seo/SeoBlogPost.tsx | 62 + blog/sections/Seo/SeoBlogPostListing.tsx | 60 + blog/sections/Template.tsx | 46 + blog/static/css.ts | 178 + blog/types.ts | 92 + blog/utils/constants.ts | 1 + blog/utils/handlePosts.ts | 87 + blog/utils/records.ts | 15 + brand-assistant/loaders/assistant.ts | 253 + brand-assistant/logo.png | Bin 0 -> 3857 bytes brand-assistant/manifest.gen.ts | 17 + brand-assistant/mod.ts | 33 + .../loaders/extensions/productDetailsPage.ts | 6 + .../loaders/extensions/productListingPage.ts | 6 + commerce/loaders/extensions/products.ts | 6 + commerce/loaders/navbar.ts | 12 + .../loaders/product/extensions/detailsPage.ts | 16 + commerce/loaders/product/extensions/list.ts | 14 + .../loaders/product/extensions/listingPage.ts | 16 + .../loaders/product/extensions/suggestions.ts | 16 + .../loaders/product/productListingPage.ts | 15 + commerce/loaders/product/products.ts | 15 + commerce/manifest.gen.ts | 45 + commerce/mod.ts | 77 + commerce/sections/Seo/SeoPDP.tsx | 43 + commerce/sections/Seo/SeoPDPV2.tsx | 81 + commerce/sections/Seo/SeoPLP.tsx | 42 + commerce/sections/Seo/SeoPLPV2.tsx | 95 + commerce/types.ts | 703 +- commerce/utils/canonical.ts | 10 + commerce/utils/constants.ts | 9 + commerce/utils/filters.ts | 10 + commerce/utils/productToAnalyticsItem.ts | 67 + commerce/utils/stateByZip.ts | 50 + compat/$live/actions/secrets/encrypt.ts | 2 + compat/$live/actions/workflows/cancel.ts | 2 + compat/$live/actions/workflows/signal.ts | 2 + compat/$live/actions/workflows/start.ts | 2 + compat/$live/flags/audience.ts | 2 + compat/$live/flags/everyone.ts | 2 + compat/$live/flags/flag.ts | 2 + compat/$live/flags/multivariate.ts | 2 + compat/$live/handlers/devPage.ts | 31 + compat/$live/handlers/fresh.ts | 2 + compat/$live/handlers/proxy.ts | 2 + compat/$live/handlers/redirect.ts | 2 + compat/$live/handlers/router.ts | 25 + compat/$live/handlers/routesSelection.ts | 2 + compat/$live/handlers/workflowRunner.ts | 2 + compat/$live/loaders/secret.ts | 10 + compat/$live/loaders/state.ts | 47 + compat/$live/loaders/workflows/events.ts | 2 + compat/$live/loaders/workflows/get.ts | 2 + compat/$live/manifest.gen.ts | 95 + compat/$live/matchers/MatchAlways.ts | 2 + compat/$live/matchers/MatchCron.ts | 2 + compat/$live/matchers/MatchDate.ts | 2 + compat/$live/matchers/MatchDevice.ts | 2 + compat/$live/matchers/MatchEnvironment.ts | 2 + compat/$live/matchers/MatchHost.ts | 2 + compat/$live/matchers/MatchLocation.ts | 2 + compat/$live/matchers/MatchMulti.ts | 2 + compat/$live/matchers/MatchRandom.ts | 2 + compat/$live/matchers/MatchSite.ts | 2 + compat/$live/matchers/MatchUserAgent.ts | 2 + compat/$live/mod.ts | 22 + compat/$live/pages/LivePage.tsx | 21 + .../$live/sections/EmptySection.tsx | 0 compat/$live/sections/PageInclude.tsx | 22 + compat/$live/sections/Slot.tsx | 38 + compat/std/deps.ts | 66 + compat/std/functions/requestToParam.ts | 1 + .../functions/vtexLegacyProductDetailsPage.ts | 15 + compat/std/functions/vtexLegacyProductList.ts | 32 + .../functions/vtexLegacyProductListingPage.ts | 19 + .../vtexLegacyRelatedProductsLoader.ts | 34 + compat/std/functions/vtexNavbar.ts | 19 + .../std/functions/vtexProductDetailsPage.ts | 15 + compat/std/functions/vtexProductList.ts | 48 + .../std/functions/vtexProductListingPage.ts | 16 + compat/std/functions/vtexSuggestions.ts | 18 + compat/std/functions/vtexWishlist.ts | 26 + .../intelligentSearch/productDetailsPage.ts | 22 + .../vtex/intelligentSearch/productList.ts | 20 + .../intelligentSearch/productListingPage.ts | 22 + .../vtex/intelligentSearch/suggestions.ts | 22 + .../loaders/vtex/legacy/productDetailsPage.ts | 20 + compat/std/loaders/vtex/legacy/productList.ts | 18 + .../loaders/vtex/legacy/productListingPage.ts | 22 + .../vtex/legacy/relatedProductsLoader.ts | 22 + compat/std/loaders/vtex/legacy/suggestions.ts | 19 + compat/std/loaders/vtex/navbar.ts | 44 + compat/std/loaders/vtex/proxy.ts | 17 + compat/std/loaders/x/font.ts | 41 + compat/std/loaders/x/redirects.ts | 42 + compat/std/manifest.gen.ts | 75 + compat/std/mod.ts | 336 + compat/std/runtime.ts | 3 + compat/std/sections/Analytics.tsx | 86 + compat/std/sections/SEOPDP.tsx | 22 + compat/std/sections/SEOPLP.tsx | 23 + .../VTEXPortalDataLayerCompatibility.tsx | 1 + crux/logo.png | Bin 0 -> 6037 bytes crux/manifest.gen.ts | 12 + crux/mod.ts | 24 + crux/preview/Preview.tsx | 36 + deco.ts | 52 +- decohub/README.md | 17 + decohub/apps/ai-assistants.ts | 1 + decohub/apps/algolia.ts | 1 + decohub/apps/analytics.ts | 1 + decohub/apps/anthropic.ts | 1 + decohub/apps/blog.ts | 1 + decohub/apps/brand-assistant.ts | 1 + decohub/apps/crux.ts | 1 + decohub/apps/emailjs.ts | 1 + decohub/apps/htmx.ts | 1 + decohub/apps/implementation.ts | 1 + decohub/apps/konfidency.ts | 1 + decohub/apps/linx-impulse.ts | 1 + decohub/apps/linx.ts | 1 + decohub/apps/mailchimp.ts | 1 + decohub/apps/nuvemshop.ts | 1 + decohub/apps/posthog.ts | 1 + decohub/apps/power-reviews.ts | 1 + decohub/apps/ra-trustvox.ts | 1 + decohub/apps/resend.ts | 1 + decohub/apps/shopify.ts | 1 + decohub/apps/smarthint.ts | 1 + decohub/apps/sourei.ts | 1 + decohub/apps/typesense.ts | 1 + decohub/apps/verified-reviews.ts | 1 + decohub/apps/vnda.ts | 1 + decohub/apps/vtex.ts | 16 + decohub/apps/wake.ts | 1 + decohub/apps/wap.ts | 3 + decohub/apps/weather.ts | 1 + decohub/apps/workflows.ts | 1 + decohub/components/Markdown.tsx | 53 + decohub/logo.png | Bin 0 -> 2209 bytes decohub/manifest.gen.ts | 75 + decohub/mod.ts | 105 + decopilot-app/README.md | 6 + decopilot-app/actions/chain/runChain.ts | 122 + decopilot-app/actions/prompt/runPrompt.ts | 56 + .../actions/prompt/runSavedPrompts.ts | 38 + decopilot-app/clients/llmClientObjects.ts | 101 + decopilot-app/deps.ts | 1 + decopilot-app/loaders/getSavedPrompts.ts | 21 + decopilot-app/loaders/listAvailableChains.ts | 45 + decopilot-app/loaders/listAvailablePrompts.ts | 45 + decopilot-app/manifest.gen.ts | 29 + decopilot-app/mod.ts | 71 + decopilot-app/preview/Preview.tsx | 353 + decopilot-app/types.ts | 190 + decopilot-app/utils/assembleComplexPrompt.ts | 140 + decopilot-app/utils/handleAttachments.ts | 109 + decopilot-app/utils/llmCaller.ts | 13 + deno.json | 62 +- emailjs/README.md | 87 + emailjs/actions/send.ts | 49 + emailjs/logo.png | Bin 0 -> 2014 bytes emailjs/manifest.gen.ts | 17 + emailjs/mod.ts | 71 + emailjs/utils/client.ts | 7 + emailjs/utils/types.ts | 30 + files/loaders/app.ts | 196 + files/manifest.gen.ts | 17 + files/mod.ts | 16 + files/sdk.ts | 183 + htmx/README.md | 7 + htmx/logo.png | Bin 0 -> 1873 bytes htmx/manifest.gen.ts | 19 + htmx/mod.ts | 73 + htmx/sections/Deferred.tsx | 89 + htmx/sections/htmx.tsx | 40 + implementation/README.md | 14 + implementation/manifest.gen.ts | 12 + implementation/mod.ts | 79 + import_map.json | 14 - konfidency/README.md | 18 + konfidency/loaders/productDetailsPage.ts | 48 + konfidency/manifest.gen.ts | 17 + konfidency/mod.ts | 44 + konfidency/utils/client.ts | 11 + konfidency/utils/transform.ts | 37 + konfidency/utils/types.ts | 44 + linx-impulse/actions/analytics/sendEvent.ts | 297 + linx-impulse/loaders/config.ts | 19 + linx-impulse/loaders/products/hotsite.ts | 103 + linx-impulse/loaders/products/linxEngage.ts | 267 + linx-impulse/loaders/products/productList.ts | 93 + .../loaders/products/productListingPage.ts | 88 + linx-impulse/loaders/products/productLists.ts | 289 + linx-impulse/loaders/products/quickFilters.ts | 26 + linx-impulse/loaders/products/suggestions.ts | 26 + linx-impulse/loaders/search/autocomplete.ts | 69 + linx-impulse/loaders/search/popular.ts | 60 + linx-impulse/loaders/search/products.ts | 55 + linx-impulse/logo.png | Bin 0 -> 6152 bytes linx-impulse/manifest.gen.ts | 47 + linx-impulse/middleware.ts | 39 + linx-impulse/mod.ts | 90 + linx-impulse/runtime.ts | 3 + .../Analytics/LinxImpulsePageView.tsx | 342 + .../sections/Script/LinxImpulseScript.tsx | 20 + linx-impulse/utils/chaordic.ts | 53 + linx-impulse/utils/client.ts | 137 + linx-impulse/utils/constants.ts | 2 + linx-impulse/utils/deviceId.ts | 7 + linx-impulse/utils/events.ts | 81 + linx-impulse/utils/source.ts | 7 + linx-impulse/utils/transform.ts | 611 + linx-impulse/utils/types/analytics.ts | 126 + linx-impulse/utils/types/chaordic.ts | 110 + linx-impulse/utils/types/impulse.ts | 73 + linx-impulse/utils/types/linx.ts | 88 + linx-impulse/utils/types/search.ts | 107 + linx/README.md | 38 + linx/actions/auction/addBid.ts | 33 + linx/actions/cart/addCoupon.ts | 32 + linx/actions/cart/addItem.ts | 76 + linx/actions/cart/simulate.ts | 74 + linx/actions/cart/updateItem.ts | 35 + linx/actions/checkout/redirect.ts | 71 + linx/actions/login.ts | 41 + linx/actions/newsletter/subscribe.ts | 27 + linx/actions/wishlist/addItem.ts | 56 + linx/actions/wishlist/addWishlist.ts | 63 + linx/actions/wishlist/removeItem.ts | 49 + linx/actions/wishlist/removeWishlist.ts | 32 + linx/actions/wishlist/shareWishlist.ts | 34 + linx/actions/wishlist/updateWishlist.ts | 64 + linx/handlers/sitemap.ts | 74 + linx/hooks/context.ts | 77 + linx/hooks/useCart.ts | 51 + linx/hooks/useUser.ts | 7 + linx/hooks/useWishlist.ts | 32 + linx/loaders/auction/ListBids.ts | 34 + linx/loaders/auction/detailsPage.ts | 45 + linx/loaders/auction/list.ts | 50 + linx/loaders/cart.ts | 56 + linx/loaders/page.ts | 29 + linx/loaders/pages.ts | 63 + linx/loaders/path.ts | 63 + linx/loaders/product/associations.ts | 20 + linx/loaders/product/byId.ts | 41 + linx/loaders/product/detailsPage.ts | 59 + linx/loaders/product/list.ts | 51 + linx/loaders/product/listingPage.ts | 112 + linx/loaders/product/suggestions.ts | 61 + linx/loaders/proxy.ts | 140 + linx/loaders/user.ts | 37 + linx/loaders/widget.ts | 25 + linx/loaders/wishlist/search.ts | 63 + linx/logo.png | Bin 0 -> 5019 bytes linx/manifest.gen.ts | 83 + linx/mod.ts | 83 + linx/runtime.ts | 3 + linx/utils/client.ts | 146 + linx/utils/headers.ts | 37 + linx/utils/layer.ts | 90 + linx/utils/paths.ts | 29 + linx/utils/transform.ts | 433 + linx/utils/types/ListBidsJSON.ts | 17 + linx/utils/types/associationsJSON.ts | 23 + linx/utils/types/auction.ts | 11 + linx/utils/types/auctionDetailJSON.ts | 318 + linx/utils/types/auctionJSON.ts | 401 + linx/utils/types/availableOptions.ts | 17 + linx/utils/types/basket.ts | 27 + linx/utils/types/basketJSON.ts | 627 + linx/utils/types/gridProductsJSON.ts | 330 + linx/utils/types/installments.ts | 11 + linx/utils/types/login.ts | 17 + linx/utils/types/newsletterJSON.ts | 44 + linx/utils/types/productByIdJSON.ts | 409 + linx/utils/types/productJSON.ts | 306 + linx/utils/types/productList.ts | 286 + linx/utils/types/shared.ts | 165 + linx/utils/types/suggestionsJSON.ts | 318 + linx/utils/types/userJSON.ts | 50 + linx/utils/types/wishlistJSON.ts | 151 + mailchimp/README.md | 21 + mailchimp/actions/subscribe.ts | 58 + mailchimp/actions/unsubscribe.ts | 31 + mailchimp/loaders/listMember.ts | 26 + mailchimp/loaders/lists.ts | 22 + mailchimp/loaders/options/lists.ts | 16 + mailchimp/logo.png | Bin 0 -> 44083 bytes mailchimp/manifest.gen.ts | 27 + mailchimp/mod.ts | 62 + mailchimp/utils/client.ts | 88 + mailchimp/utils/transform.ts | 14 + mailchimp/utils/types.ts | 142 + nuvemshop/README.MD | 5 + nuvemshop/actions/cart/addItems.ts | 67 + nuvemshop/actions/cart/updateItems.ts | 59 + nuvemshop/handlers/sitemap.ts | 75 + nuvemshop/hooks/context.ts | 66 + nuvemshop/hooks/useCart.ts | 47 + nuvemshop/loaders/cart.ts | 25 + nuvemshop/loaders/productDetailsPage.ts | 61 + nuvemshop/loaders/productList.ts | 54 + nuvemshop/loaders/productListingPage.ts | 116 + nuvemshop/loaders/proxy.ts | 151 + nuvemshop/logo.png | Bin 0 -> 7512 bytes nuvemshop/manifest.gen.ts | 35 + nuvemshop/mod.ts | 112 + nuvemshop/preview/index.tsx | 54 + nuvemshop/runtime.ts | 3 + nuvemshop/utils/cart.ts | 46 + nuvemshop/utils/client.ts | 19 + nuvemshop/utils/transform.ts | 318 + nuvemshop/utils/types.ts | 375 + openai/deps.ts | 1 + openai/loaders/vision.ts | 46 + openai/manifest.gen.ts | 17 + openai/mod.ts | 32 + posthog/components/PostHog.tsx | 106 + posthog/manifest.gen.ts | 17 + posthog/mod.ts | 42 + posthog/sections/Analytics/PostHog.tsx | 14 + power-reviews/actions/submitReview.ts | 42 + power-reviews/loaders/productDetailsPage.ts | 80 + power-reviews/loaders/productList.ts | 64 + power-reviews/loaders/productListingPage.ts | 83 + power-reviews/loaders/review.ts | 103 + power-reviews/loaders/reviewForm.ts | 40 + power-reviews/logo.png | Bin 0 -> 6803 bytes power-reviews/manifest.gen.ts | 35 + power-reviews/mod.ts | 116 + power-reviews/readme.md | 31 + power-reviews/sections/Question.tsx | 80 + power-reviews/sections/WriteReviewForm.tsx | 53 + power-reviews/utils/client.ts | 39 + power-reviews/utils/tranform.ts | 77 + power-reviews/utils/types.ts | 214 + .../components/TrustvoxCertificateFixed.tsx | 5 + .../components/TrustvoxProductDetailsRate.tsx | 50 + ra-trustvox/components/TrustvoxShelfRate.tsx | 31 + ra-trustvox/manifest.gen.ts | 23 + ra-trustvox/mod.ts | 46 + ra-trustvox/ra-trustvox.png | Bin 0 -> 14426 bytes ra-trustvox/sections/TrustvoxCertificate.tsx | 15 + .../sections/TrustvoxProductReviews.tsx | 65 + ra-trustvox/sections/TrustvoxRateConfig.tsx | 36 + .../sections/TrustvoxStoreReviewsCarousel.tsx | 53 + records/deps.ts | 5 + records/loaders/drizzle.ts | 5 + records/loaders/sqlClient.ts | 9 + records/logo.png | Bin 0 -> 2891 bytes records/manifest.gen.ts | 19 + records/mod.ts | 50 + records/scripts/checkDbCredential.ts | 38 + records/scripts/pullProd.ts | 99 + records/utils.ts | 46 + resend/README.md | 82 + resend/actions/emails/send.ts | 30 + resend/logo.png | Bin 0 -> 12621 bytes resend/mailExamples/StripeWelcomeEmail.tsx | 144 + resend/manifest.gen.ts | 17 + resend/mod.ts | 93 + resend/utils/client.ts | 7 + resend/utils/reactEmail.ts | 34 + resend/utils/types.ts | 45 + sap/README.md | 5 + sap/loaders/categories/tree.ts | 48 + sap/loaders/product/ProductList.ts | 45 + sap/loaders/product/productDetailsPage.ts | 53 + sap/loaders/product/productListingPage.ts | 107 + sap/manifest.gen.ts | 23 + sap/mod.ts | 71 + sap/runtime.ts | 4 + sap/utils/client/client.ts | 21 + sap/utils/constants.ts | 0 sap/utils/transform.ts | 282 + sap/utils/types.ts | 696 + scripts/new.ts | 37 + scripts/start.ts | 264 + shopify/README.md | 8 + shopify/actions/cart/addItems.ts | 44 + shopify/actions/cart/updateCoupons.ts | 39 + shopify/actions/cart/updateItems.ts | 42 + shopify/actions/order/draftOrderCalculate.ts | 59 + shopify/actions/user/signIn.ts | 63 + shopify/handlers/sitemap.ts | 76 + shopify/hooks/context.ts | 71 + shopify/hooks/useCart.ts | 52 + shopify/hooks/useUser.ts | 7 + shopify/loaders/ProductDetailsPage.ts | 67 + shopify/loaders/ProductList.ts | 208 + shopify/loaders/ProductListingPage.ts | 247 + shopify/loaders/RelatedProducts.ts | 95 + shopify/loaders/cart.ts | 38 + shopify/loaders/proxy.ts | 153 + shopify/loaders/user.ts | 44 + shopify/logo.png | Bin 0 -> 10626 bytes shopify/manifest.gen.ts | 45 + shopify/mod.ts | 126 + shopify/runtime.ts | 3 + shopify/utils/admin/admin.graphql.gen.ts | 44523 ++++ shopify/utils/admin/admin.graphql.json | 194416 +++++++++++++++ shopify/utils/admin/queries.ts | 29 + shopify/utils/cart.ts | 28 + shopify/utils/enums.ts | 438 + shopify/utils/password.ts | 9 + shopify/utils/storefront/queries.ts | 427 + .../storefront/storefront.graphql.gen.ts | 7814 + .../utils/storefront/storefront.graphql.json | 35174 +++ shopify/utils/transform.ts | 297 + shopify/utils/types.ts | 191 + shopify/utils/user.ts | 23 + shopify/utils/utils.ts | 150 + smarthint/README.md | 70 + smarthint/actions/click.ts | 69 + smarthint/actions/pageview.ts | 54 + smarthint/components/Click.tsx | 31 + smarthint/hooks/useAutocomplete.ts | 43 + smarthint/loaders/PLPBanners.ts | 108 + smarthint/loaders/autocomplete.ts | 60 + smarthint/loaders/banners.ts | 47 + smarthint/loaders/productListingPage.ts | 154 + smarthint/loaders/recommendations.ts | 191 + smarthint/logo.png | Bin 0 -> 41342 bytes smarthint/manifest.gen.ts | 35 + smarthint/mod.ts | 86 + smarthint/runtime.ts | 3 + .../sections/Analytics/SmarthintTracking.tsx | 204 + smarthint/utils/getSession.ts | 13 + .../utils/openapi/smarthint.openapi.gen.ts | 1279 + .../utils/openapi/smarthint.openapi.json | 8398 + smarthint/utils/sortPagesPattern.ts | 31 + smarthint/utils/transform.ts | 469 + smarthint/utils/typings.ts | 431 + sourei/README.md | 14 + sourei/logo.png | Bin 0 -> 1767 bytes sourei/logo.svg | 33 + sourei/manifest.gen.ts | 17 + sourei/mod.ts | 27 + sourei/sections/Analytics/Sourei.tsx | 94 + typesense/README.md | 35 + typesense/actions/index/product.ts | 26 + typesense/fly.toml | 27 + typesense/loaders/product/list.ts | 48 + typesense/loaders/product/listingPage.ts | 213 + typesense/logo.png | Bin 0 -> 4483 bytes typesense/manifest.gen.ts | 27 + typesense/mod.ts | 97 + typesense/utils/highlight.ts | 36 + typesense/utils/once.ts | 28 + typesense/utils/product.ts | 276 + typesense/workflows/index/product.ts | 35 + utils/HttpError.ts | 6 - utils/capitalize.ts | 9 + utils/components/Slider.tsx | 42 + utils/components/SliderJS.tsx | 205 + utils/cookie.ts | 39 + utils/dataURI.ts | 31 + utils/defaultErrorPage.tsx | 185 + utils/deferred.ts | 5 + utils/fetch.ts | 92 +- utils/framework.tsx | 28 + utils/graphql.ts | 67 + utils/http.ts | 168 + utils/lru.ts | 27 + utils/normalize.ts | 37 + utils/preview.tsx | 181 + utils/shortHash.ts | 21 + utils/weakcache.ts | 1 + .../loaders/productDetailsPage.ts | 45 + verified-reviews/loaders/productList.ts | 39 + .../loaders/productListingPage.ts | 47 + verified-reviews/loaders/storeReview.ts | 42 + verified-reviews/logo.png | Bin 0 -> 21768 bytes verified-reviews/manifest.gen.ts | 23 + verified-reviews/mod.ts | 36 + verified-reviews/utils/client.ts | 186 + verified-reviews/utils/transform.ts | 40 + verified-reviews/utils/types.ts | 51 + vnda/README.md | 11 + vnda/actions/cart/addItem.ts | 42 +- vnda/actions/cart/addItems.ts | 46 + vnda/actions/cart/setShippingAddress.ts | 24 - vnda/actions/cart/simulation.ts | 28 + vnda/actions/cart/updateCart.ts | 31 + vnda/actions/cart/updateCoupon.ts | 23 - vnda/actions/cart/updateItem.ts | 22 +- vnda/actions/notifyme.ts | 43 + vnda/doccache.zst | Bin 47369 -> 0 bytes vnda/handlers/sitemap.ts | 73 + vnda/hooks/context.ts | 12 +- vnda/hooks/useCart.ts | 69 +- vnda/loaders/cart.ts | 46 +- vnda/loaders/extensions/price/list.ts | 21 + vnda/loaders/extensions/price/listingPage.ts | 35 + vnda/loaders/productDetailsPage.ts | 99 +- vnda/loaders/productDetailsPageVideo.ts | 28 + vnda/loaders/productList.ts | 50 +- vnda/loaders/productListingPage.ts | 225 +- vnda/loaders/proxy.ts | 133 + vnda/logo.png | Bin 0 -> 17387 bytes vnda/manifest.gen.ts | 55 +- vnda/middleware.ts | 29 + vnda/mod.ts | 87 +- vnda/runtime.ts | 7 +- vnda/utils/cart.ts | 29 + vnda/utils/client/client.ts | 302 +- vnda/utils/client/types.ts | 33 +- vnda/utils/constants.ts | 0 vnda/utils/openapi/vnda.openapi.gen.ts | 5412 + vnda/utils/openapi/vnda.openapi.json | 19471 ++ vnda/utils/paths.ts | 30 - vnda/utils/queryBuilder.ts | 24 - vnda/utils/segment.ts | 51 + vnda/utils/transform.ts | 360 +- vtex/README.md | 6 + vtex/actions/address/createAddress.ts | 67 + vtex/actions/address/deleteAddress.ts | 58 + vtex/actions/address/updateAddress.ts | 92 + vtex/actions/analytics/sendEvent.ts | 76 + vtex/actions/cart/addItems.ts | 63 + vtex/actions/cart/addOfferings.ts | 52 + vtex/actions/cart/clearOrderformMessages.ts | 30 + vtex/actions/cart/getInstallment.ts | 36 + vtex/actions/cart/removeItemAttachment.ts | 70 + vtex/actions/cart/removeItems.ts | 37 + vtex/actions/cart/removeOffering.ts | 54 + vtex/actions/cart/simulation.ts | 51 + vtex/actions/cart/updateAttachment.ts | 49 + vtex/actions/cart/updateCoupons.ts | 43 + vtex/actions/cart/updateGifts.ts | 40 + vtex/actions/cart/updateItemAttachment.ts | 75 + vtex/actions/cart/updateItemPrice.ts | 48 + vtex/actions/cart/updateItems.ts | 53 + vtex/actions/cart/updateProfile.ts | 43 + vtex/actions/cart/updateUser.ts | 36 + vtex/actions/masterdata/createDocument.ts | 47 + vtex/actions/newsletter/subscribe.ts | 37 + vtex/actions/notifyme.ts | 28 + vtex/actions/payments/delete.ts | 39 + vtex/actions/profile/newsletterProfile.ts | 60 + vtex/actions/profile/updateProfile.ts | 93 + vtex/actions/review/submit.ts | 44 + vtex/actions/sessions/delete.ts | 39 + vtex/actions/trigger.ts | 46 + vtex/actions/wishlist/addItem.ts | 37 + vtex/actions/wishlist/removeItem.ts | 36 + .../VTEXPortalDataLayerCompatibility.tsx | 200 + vtex/doccache.zst | Bin 11 -> 0 bytes vtex/handlers/sitemap.ts | 77 + vtex/hooks/context.ts | 78 + vtex/hooks/useAutocomplete.ts | 33 + vtex/hooks/useCart.ts | 99 + vtex/hooks/useUser.ts | 7 + vtex/hooks/useWishlist.ts | 33 + vtex/loaders/address/getAddressByZIP.ts | 59 + vtex/loaders/address/list.ts | 86 + vtex/loaders/cart.ts | 89 + vtex/loaders/categories/tree.ts | 23 + vtex/loaders/collections/list.ts | 35 + vtex/loaders/config.ts | 25 + .../intelligentSearch/productDetailsPage.ts | 169 + vtex/loaders/intelligentSearch/productList.ts | 329 + .../intelligentSearch/productListingPage.ts | 416 + .../productSearchValidator.ts | 46 + vtex/loaders/intelligentSearch/suggestions.ts | 95 + vtex/loaders/intelligentSearch/topsearches.ts | 18 + vtex/loaders/legacy/brands.ts | 47 + vtex/loaders/legacy/pageType.ts | 25 + vtex/loaders/legacy/productDetailsPage.ts | 129 + vtex/loaders/legacy/productList.ts | 328 + vtex/loaders/legacy/productListingPage.ts | 449 + vtex/loaders/legacy/relatedProductsLoader.ts | 172 + vtex/loaders/legacy/suggestions.ts | 84 + vtex/loaders/logistics/listPickupPoints.ts | 17 + .../logistics/listPickupPointsByLocation.ts | 46 + vtex/loaders/masterdata/searchDocuments.ts | 68 + vtex/loaders/navbar.ts | 35 + vtex/loaders/options/productIdByTerm.ts | 29 + vtex/loaders/orders/list.ts | 41 + vtex/loaders/orders/order.ts | 39 + vtex/loaders/paths/PDPDefaultPath.ts | 40 + vtex/loaders/paths/PLPDefaultPath.ts | 55 + vtex/loaders/payments/info.ts | 55 + vtex/loaders/payments/userPayments.ts | 61 + vtex/loaders/product/extend.ts | 216 + .../loaders/product/extensions/detailsPage.ts | 33 + vtex/loaders/product/extensions/list.ts | 23 + .../loaders/product/extensions/listingPage.ts | 33 + .../loaders/product/extensions/suggestions.ts | 31 + vtex/loaders/product/wishlist.ts | 72 + vtex/loaders/profile/passwordLastUpdate.ts | 39 + vtex/loaders/proxy.ts | 208 + vtex/loaders/sessions/info.ts | 68 + vtex/loaders/user.ts | 68 + vtex/loaders/wishlist.ts | 66 + vtex/loaders/workflow/product.ts | 198 + vtex/loaders/workflow/products.ts | 31 + vtex/logo.png | Bin 0 -> 3093 bytes vtex/manifest.gen.ts | 176 +- vtex/middleware.ts | 30 + vtex/mod.ts | 163 +- vtex/preview/Preview.tsx | 181 + vtex/runtime.ts | 3 + vtex/sections/Analytics/Vtex.tsx | 112 + vtex/utils/batch.ts | 21 + vtex/utils/cacheBySegment.ts | 12 + vtex/utils/client.ts | 265 + vtex/utils/cookies.ts | 25 + vtex/utils/extensions/simulation.ts | 140 + vtex/utils/fetchVTEX.ts | 78 + vtex/utils/intelligentSearch.ts | 165 + vtex/utils/legacy.ts | 164 + vtex/utils/openapi/api.openapi.gen.ts | 762 + vtex/utils/openapi/api.openapi.json | 2183 + vtex/utils/openapi/my.openapi.gen.ts | 313 + vtex/utils/openapi/my.openapi.json | 801 + vtex/utils/openapi/vcs.openapi.gen.ts | 21186 ++ vtex/utils/openapi/vcs.openapi.json | 42331 ++++ vtex/utils/orderForm.ts | 65 + vtex/utils/resourceRange.ts | 12 + vtex/utils/segment.ts | 206 + vtex/utils/similars.ts | 29 + vtex/utils/slugify.ts | 13 + vtex/utils/transform.ts | 1185 + vtex/utils/types.ts | 1494 + vtex/utils/vtexId.ts | 36 + vtex/workflows/events.ts | 50 + vtex/workflows/product/index.ts | 46 + wake/README.md | 9 + wake/actions/cart/addCoupon.ts | 46 + wake/actions/cart/addItem.ts | 15 + wake/actions/cart/addItems.ts | 53 + wake/actions/cart/addKit.ts | 59 + wake/actions/cart/partnerAssociate.ts | 46 + wake/actions/cart/partnerDisassociate.ts | 42 + wake/actions/cart/removeCoupon.ts | 42 + wake/actions/cart/removeKit.ts | 58 + wake/actions/cart/updateItemQuantity.ts | 87 + wake/actions/newsletter/register.ts | 41 + wake/actions/notifyme.ts | 38 + wake/actions/review/create.ts | 40 + wake/actions/shippingSimulation.ts | 80 + wake/actions/submmitForm.ts | 37 + wake/actions/wishlist/addProduct.ts | 50 + wake/actions/wishlist/removeProduct.ts | 53 + wake/handlers/sitemap.ts | 78 + wake/hooks/context.ts | 144 + wake/hooks/useCart.ts | 57 + wake/hooks/useUser.ts | 7 + wake/hooks/useWishlist.ts | 34 + wake/loaders/cart.ts | 48 + wake/loaders/partners.ts | 39 + wake/loaders/productDetailsPage.ts | 157 + wake/loaders/productList.ts | 180 + wake/loaders/productListingPage.ts | 317 + wake/loaders/proxy.ts | 96 + wake/loaders/recommendations.ts | 74 + wake/loaders/shop.ts | 32 + wake/loaders/suggestion.ts | 59 + wake/loaders/user.ts | 56 + wake/loaders/wishlist.ts | 43 + wake/logo.png | Bin 0 -> 4857 bytes wake/manifest.gen.ts | 75 + wake/mod.ts | 110 + wake/runtime.ts | 3 + wake/utils/authenticate.ts | 25 + wake/utils/cart.ts | 28 + wake/utils/client.ts | 16 + wake/utils/getVariations.ts | 53 + wake/utils/graphql/queries.ts | 1371 + wake/utils/graphql/storefront.graphql.gen.ts | 5274 + wake/utils/graphql/storefront.graphql.json | 30078 +++ wake/utils/openapi/wake.openapi.gen.ts | 9858 + wake/utils/openapi/wake.openapi.json | 45132 ++++ wake/utils/parseHeaders.ts | 18 + wake/utils/transform.ts | 476 + wake/utils/user.ts | 9 + wap/actions/cart/addItems.ts | 38 + wap/actions/cart/removeItem.ts | 35 + wap/actions/cart/updateCoupon.ts | 29 + wap/actions/cart/updateItem.ts | 37 + wap/actions/evaluations/product.ts | 33 + wap/actions/forms/contact.ts | 33 + wap/actions/newsletter/associates.ts | 42 + wap/actions/newsletter/register.ts | 33 + wap/actions/product/solicitation.ts | 34 + wap/actions/question/answer.ts | 32 + wap/actions/question/question.ts | 33 + wap/actions/shipment/product.ts | 45 + wap/actions/wishlist/addItem.ts | 32 + wap/actions/wishlist/removeItem.ts | 32 + wap/hooks/context.ts | 80 + wap/hooks/useCart.ts | 46 + wap/hooks/useUser.ts | 7 + wap/hooks/useWishlist.ts | 31 + wap/loaders/cart.ts | 26 + wap/loaders/productDetailsPage.ts | 55 + wap/loaders/productList.ts | 59 + wap/loaders/productListIndicated.ts | 62 + wap/loaders/productListLastSeen.ts | 39 + wap/loaders/productListingPage.ts | 184 + wap/loaders/proxy.ts | 45 + wap/loaders/suggestions.ts | 44 + wap/loaders/user.ts | 36 + wap/loaders/wishlist.ts | 22 + wap/manifest.gen.ts | 65 + wap/mod.ts | 62 + wap/runtime.ts | 3 + wap/utils/cart.ts | 9 + wap/utils/openapi/api.openapi.gen.ts | 639 + wap/utils/openapi/api.openapi.json | 3785 + wap/utils/transform.ts | 434 + wap/utils/type.ts | 1051 + weather/loaders/temperature.ts | 44 + weather/logo.png | Bin 0 -> 3353 bytes weather/manifest.gen.ts | 25 + weather/matchers/temperature.ts | 36 + weather/mod.ts | 30 + weather/sections/WhatsTheTemperature.tsx | 19 + website/Preview.tsx | 29 + website/actions/secrets/encrypt.ts | 16 +- website/components/Analytics.tsx | 157 + website/components/Clickhouse.tsx | 311 + website/components/Events.tsx | 106 + website/components/Image.tsx | 167 +- website/components/Picture.tsx | 1 + website/components/PoweredByDeco.tsx | 53 + website/components/Seo.tsx | 108 + website/components/Theme.tsx | 58 + website/components/Video.tsx | 5 - website/components/_Analytics.tsx | 133 - website/components/_Controls.tsx | 132 +- .../_seo}/Discord.tsx | 25 +- website/components/_seo/Facebook.tsx | 140 + website/components/_seo/Google.tsx | 59 + website/components/_seo/Icons.tsx | 304 + website/components/_seo/LinkedIn.tsx | 49 + website/components/_seo/Preview.tsx | 194 + .../components => components/_seo}/Slack.tsx | 61 +- website/components/_seo/Telegram.tsx | 112 + .../_seo}/Twitter.tsx | 75 +- website/components/_seo/WhatsApp.tsx | 97 + .../_seo}/helpers/textShortner.tsx | 0 .../_seo}/instructions.json | 0 website/doccache.zst | Bin 55245 -> 0 bytes website/flags/audience.ts | 27 +- website/flags/everyone.ts | 23 +- website/flags/flag.ts | 13 +- website/flags/multivariate.ts | 60 +- website/flags/multivariate/image.ts | 12 + website/flags/multivariate/message.ts | 12 + website/flags/multivariate/page.ts | 11 + website/flags/multivariate/section.ts | 11 + website/functions/requestToParam.ts | 20 +- website/handlers/fresh.ts | 193 +- website/handlers/proxy.ts | 251 +- website/handlers/redirect.ts | 30 +- website/handlers/router.ts | 267 +- website/handlers/sitemap.ts | 34 +- website/loaders/asset.ts | 18 +- website/loaders/extension.ts | 29 + website/loaders/fonts/googleFonts.ts | 137 + website/loaders/fonts/local.ts | 7 + website/loaders/image.ts | 16 +- website/loaders/options/routes.ts | 25 + website/loaders/options/urlParams.ts | 18 + website/loaders/pages.ts | 52 + website/loaders/redirect.ts | 11 + website/loaders/redirects.ts | 44 + website/loaders/redirectsFromCsv.ts | 112 +- website/loaders/secret.ts | 72 +- website/loaders/secretString.ts | 18 + website/logo.png | Bin 0 -> 12933 bytes website/manifest.gen.ts | 163 +- website/matchers/always.ts | 2 + website/matchers/cookie.ts | 19 + website/matchers/cron.ts | 5 +- website/matchers/date.ts | 4 +- website/matchers/device.ts | 39 +- website/matchers/environment.ts | 4 +- website/matchers/host.ts | 15 +- website/matchers/location.ts | 91 +- website/matchers/multi.ts | 9 +- website/matchers/negate.ts | 16 + website/matchers/never.ts | 10 + website/matchers/pathname.ts | 52 + website/matchers/queryString.ts | 124 + website/matchers/random.ts | 4 +- website/matchers/site.ts | 9 +- website/matchers/userAgent.ts | 10 +- website/mod.ts | 241 +- website/pages/LivePage.tsx | 263 - website/pages/Page.tsx | 237 + website/sections/Analytics/Analytics.tsx | 7 + website/sections/Rendering/Deferred.tsx | 135 + website/sections/Rendering/Lazy.tsx | 124 + website/sections/Rendering/SingleDeferred.tsx | 2 + website/sections/Seo/Seo.tsx | 16 + website/sections/Seo/SeoV2.tsx | 44 + website/seo/Head.tsx | 1 - website/seo/Metatags.tsx | 62 - website/seo/ScriptLDJson.tsx | 21 - website/seo/components/Facebook.tsx | 119 - website/seo/components/Google.tsx | 23 - website/seo/components/LinkedIn.tsx | 28 - website/seo/components/Preview.tsx | 111 - website/seo/components/PreviewItem.tsx | 27 - website/seo/components/Telegram.tsx | 79 - website/seo/components/WhatsApp.tsx | 72 - website/seo/components/fragments/Title.tsx | 16 - website/seo/types.ts | 57 - website/seo/utils.ts | 65 - website/types.ts | 1 + website/utils/crypto.ts | 43 +- website/utils/html.ts | 5 + .../utils/image/engines/passThrough/engine.ts | 4 +- website/utils/image/engines/remote/engine.ts | 2 +- website/utils/image/engines/wasm/engine.ts | 2 +- website/utils/location.ts | 25 + website/utils/multivariate.ts | 66 + website/utils/unhandledRejection.ts | 9 + workflows/actions/start.ts | 62 +- workflows/deps.ts | 2 +- workflows/doccache.zst | Bin 47200 -> 0 bytes workflows/handlers/workflowRunner.ts | 20 +- workflows/initializer.ts | 75 +- workflows/loaders/events.ts | 9 +- workflows/loaders/get.ts | 3 +- workflows/logo.png | Bin 0 -> 2114 bytes workflows/manifest.gen.ts | 29 +- workflows/mod.ts | 50 +- workflows/utils/awaiters.ts | 19 + 906 files changed, 538532 insertions(+), 2773 deletions(-) create mode 100644 .DS_Store create mode 100644 .github/issue_template.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/release.yaml create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/issues.yaml create mode 100644 .github/workflows/release.yaml create mode 100644 .github/workflows/releaser.yaml create mode 100644 CODE-OF-CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 MAINTAINERS.txt create mode 100644 README.md create mode 100644 admin/types.ts create mode 100644 admin/widgets.ts create mode 100644 ai-assistants/actions/awsUploadImage.ts create mode 100644 ai-assistants/actions/chat.ts create mode 100644 ai-assistants/actions/describeImage.ts create mode 100644 ai-assistants/actions/transcribeAudio.ts create mode 100644 ai-assistants/chat/messages.ts create mode 100644 ai-assistants/deps.ts create mode 100644 ai-assistants/hooks/useFileUpload.ts create mode 100644 ai-assistants/loaders/messages.ts create mode 100644 ai-assistants/logo.png create mode 100644 ai-assistants/manifest.gen.ts create mode 100644 ai-assistants/mod.ts create mode 100644 ai-assistants/runtime.ts create mode 100644 ai-assistants/schema.ts create mode 100644 ai-assistants/types.ts create mode 100644 ai-assistants/utils/blobConversion.ts create mode 100644 algolia/README.md create mode 100644 algolia/actions/index/product.ts create mode 100644 algolia/actions/index/wait.ts create mode 100644 algolia/actions/setup.ts create mode 100644 algolia/loaders/client.ts create mode 100644 algolia/loaders/product/list.ts create mode 100644 algolia/loaders/product/listingPage.ts create mode 100644 algolia/loaders/product/suggestions.ts create mode 100644 algolia/logo.png create mode 100644 algolia/manifest.gen.ts create mode 100644 algolia/mod.ts create mode 100644 algolia/sections/Analytics/Algolia.tsx create mode 100644 algolia/utils/highlight.ts create mode 100644 algolia/utils/product.ts create mode 100644 algolia/workflows/index/product.ts create mode 100644 analytics/components/DecoAnalytics.tsx create mode 100644 analytics/loaders/DecoAnalyticsScript.ts create mode 100644 analytics/logo.png create mode 100644 analytics/manifest.gen.ts create mode 100644 analytics/mod.ts create mode 100644 analytics/scripts/plausible_scripts.ts create mode 100644 analytics/sections/Analytics/DecoAnalytics.tsx create mode 100644 anthropic/actions/code.ts create mode 100644 anthropic/actions/invoke.ts create mode 100644 anthropic/actions/stream.ts create mode 100644 anthropic/deps.ts create mode 100644 anthropic/manifest.gen.ts create mode 100644 anthropic/mod.ts create mode 100644 anthropic/utils.ts create mode 100644 assets/Konfidency.svg create mode 100644 assets/logo.svg create mode 100644 blog/loaders/Author.ts create mode 100644 blog/loaders/BlogPostItem.ts create mode 100644 blog/loaders/BlogPostPage.ts create mode 100644 blog/loaders/Blogpost.ts create mode 100644 blog/loaders/BlogpostList.ts create mode 100644 blog/loaders/BlogpostListing.ts create mode 100644 blog/loaders/Category.ts create mode 100644 blog/loaders/GetCategories.ts create mode 100644 blog/manifest.gen.ts create mode 100644 blog/mod.ts create mode 100644 blog/sections/Seo/SeoBlogPost.tsx create mode 100644 blog/sections/Seo/SeoBlogPostListing.tsx create mode 100644 blog/sections/Template.tsx create mode 100644 blog/static/css.ts create mode 100644 blog/types.ts create mode 100644 blog/utils/constants.ts create mode 100644 blog/utils/handlePosts.ts create mode 100644 blog/utils/records.ts create mode 100644 brand-assistant/loaders/assistant.ts create mode 100644 brand-assistant/logo.png create mode 100644 brand-assistant/manifest.gen.ts create mode 100644 brand-assistant/mod.ts create mode 100644 commerce/loaders/extensions/productDetailsPage.ts create mode 100644 commerce/loaders/extensions/productListingPage.ts create mode 100644 commerce/loaders/extensions/products.ts create mode 100644 commerce/loaders/navbar.ts create mode 100644 commerce/loaders/product/extensions/detailsPage.ts create mode 100644 commerce/loaders/product/extensions/list.ts create mode 100644 commerce/loaders/product/extensions/listingPage.ts create mode 100644 commerce/loaders/product/extensions/suggestions.ts create mode 100644 commerce/loaders/product/productListingPage.ts create mode 100644 commerce/loaders/product/products.ts create mode 100644 commerce/manifest.gen.ts create mode 100644 commerce/mod.ts create mode 100644 commerce/sections/Seo/SeoPDP.tsx create mode 100644 commerce/sections/Seo/SeoPDPV2.tsx create mode 100644 commerce/sections/Seo/SeoPLP.tsx create mode 100644 commerce/sections/Seo/SeoPLPV2.tsx create mode 100644 commerce/utils/canonical.ts create mode 100644 commerce/utils/constants.ts create mode 100644 commerce/utils/filters.ts create mode 100644 commerce/utils/productToAnalyticsItem.ts create mode 100644 commerce/utils/stateByZip.ts create mode 100644 compat/$live/actions/secrets/encrypt.ts create mode 100644 compat/$live/actions/workflows/cancel.ts create mode 100644 compat/$live/actions/workflows/signal.ts create mode 100644 compat/$live/actions/workflows/start.ts create mode 100644 compat/$live/flags/audience.ts create mode 100644 compat/$live/flags/everyone.ts create mode 100644 compat/$live/flags/flag.ts create mode 100644 compat/$live/flags/multivariate.ts create mode 100644 compat/$live/handlers/devPage.ts create mode 100644 compat/$live/handlers/fresh.ts create mode 100644 compat/$live/handlers/proxy.ts create mode 100644 compat/$live/handlers/redirect.ts create mode 100644 compat/$live/handlers/router.ts create mode 100644 compat/$live/handlers/routesSelection.ts create mode 100644 compat/$live/handlers/workflowRunner.ts create mode 100644 compat/$live/loaders/secret.ts create mode 100644 compat/$live/loaders/state.ts create mode 100644 compat/$live/loaders/workflows/events.ts create mode 100644 compat/$live/loaders/workflows/get.ts create mode 100644 compat/$live/manifest.gen.ts create mode 100644 compat/$live/matchers/MatchAlways.ts create mode 100644 compat/$live/matchers/MatchCron.ts create mode 100644 compat/$live/matchers/MatchDate.ts create mode 100644 compat/$live/matchers/MatchDevice.ts create mode 100644 compat/$live/matchers/MatchEnvironment.ts create mode 100644 compat/$live/matchers/MatchHost.ts create mode 100644 compat/$live/matchers/MatchLocation.ts create mode 100644 compat/$live/matchers/MatchMulti.ts create mode 100644 compat/$live/matchers/MatchRandom.ts create mode 100644 compat/$live/matchers/MatchSite.ts create mode 100644 compat/$live/matchers/MatchUserAgent.ts create mode 100644 compat/$live/mod.ts create mode 100644 compat/$live/pages/LivePage.tsx rename website/sections/Empty.tsx => compat/$live/sections/EmptySection.tsx (100%) create mode 100644 compat/$live/sections/PageInclude.tsx create mode 100644 compat/$live/sections/Slot.tsx create mode 100644 compat/std/deps.ts create mode 100644 compat/std/functions/requestToParam.ts create mode 100644 compat/std/functions/vtexLegacyProductDetailsPage.ts create mode 100644 compat/std/functions/vtexLegacyProductList.ts create mode 100644 compat/std/functions/vtexLegacyProductListingPage.ts create mode 100644 compat/std/functions/vtexLegacyRelatedProductsLoader.ts create mode 100644 compat/std/functions/vtexNavbar.ts create mode 100644 compat/std/functions/vtexProductDetailsPage.ts create mode 100644 compat/std/functions/vtexProductList.ts create mode 100644 compat/std/functions/vtexProductListingPage.ts create mode 100644 compat/std/functions/vtexSuggestions.ts create mode 100644 compat/std/functions/vtexWishlist.ts create mode 100644 compat/std/loaders/vtex/intelligentSearch/productDetailsPage.ts create mode 100644 compat/std/loaders/vtex/intelligentSearch/productList.ts create mode 100644 compat/std/loaders/vtex/intelligentSearch/productListingPage.ts create mode 100644 compat/std/loaders/vtex/intelligentSearch/suggestions.ts create mode 100644 compat/std/loaders/vtex/legacy/productDetailsPage.ts create mode 100644 compat/std/loaders/vtex/legacy/productList.ts create mode 100644 compat/std/loaders/vtex/legacy/productListingPage.ts create mode 100644 compat/std/loaders/vtex/legacy/relatedProductsLoader.ts create mode 100644 compat/std/loaders/vtex/legacy/suggestions.ts create mode 100644 compat/std/loaders/vtex/navbar.ts create mode 100644 compat/std/loaders/vtex/proxy.ts create mode 100644 compat/std/loaders/x/font.ts create mode 100644 compat/std/loaders/x/redirects.ts create mode 100644 compat/std/manifest.gen.ts create mode 100644 compat/std/mod.ts create mode 100644 compat/std/runtime.ts create mode 100644 compat/std/sections/Analytics.tsx create mode 100644 compat/std/sections/SEOPDP.tsx create mode 100644 compat/std/sections/SEOPLP.tsx create mode 100644 compat/std/sections/VTEXPortalDataLayerCompatibility.tsx create mode 100644 crux/logo.png create mode 100644 crux/manifest.gen.ts create mode 100644 crux/mod.ts create mode 100644 crux/preview/Preview.tsx create mode 100644 decohub/README.md create mode 100644 decohub/apps/ai-assistants.ts create mode 100644 decohub/apps/algolia.ts create mode 100644 decohub/apps/analytics.ts create mode 100644 decohub/apps/anthropic.ts create mode 100644 decohub/apps/blog.ts create mode 100644 decohub/apps/brand-assistant.ts create mode 100644 decohub/apps/crux.ts create mode 100644 decohub/apps/emailjs.ts create mode 100644 decohub/apps/htmx.ts create mode 100644 decohub/apps/implementation.ts create mode 100644 decohub/apps/konfidency.ts create mode 100644 decohub/apps/linx-impulse.ts create mode 100644 decohub/apps/linx.ts create mode 100644 decohub/apps/mailchimp.ts create mode 100644 decohub/apps/nuvemshop.ts create mode 100644 decohub/apps/posthog.ts create mode 100644 decohub/apps/power-reviews.ts create mode 100644 decohub/apps/ra-trustvox.ts create mode 100644 decohub/apps/resend.ts create mode 100644 decohub/apps/shopify.ts create mode 100644 decohub/apps/smarthint.ts create mode 100644 decohub/apps/sourei.ts create mode 100644 decohub/apps/typesense.ts create mode 100644 decohub/apps/verified-reviews.ts create mode 100644 decohub/apps/vnda.ts create mode 100644 decohub/apps/vtex.ts create mode 100644 decohub/apps/wake.ts create mode 100644 decohub/apps/wap.ts create mode 100644 decohub/apps/weather.ts create mode 100644 decohub/apps/workflows.ts create mode 100644 decohub/components/Markdown.tsx create mode 100644 decohub/logo.png create mode 100644 decohub/manifest.gen.ts create mode 100644 decohub/mod.ts create mode 100644 decopilot-app/README.md create mode 100644 decopilot-app/actions/chain/runChain.ts create mode 100644 decopilot-app/actions/prompt/runPrompt.ts create mode 100644 decopilot-app/actions/prompt/runSavedPrompts.ts create mode 100644 decopilot-app/clients/llmClientObjects.ts create mode 100644 decopilot-app/deps.ts create mode 100644 decopilot-app/loaders/getSavedPrompts.ts create mode 100644 decopilot-app/loaders/listAvailableChains.ts create mode 100644 decopilot-app/loaders/listAvailablePrompts.ts create mode 100644 decopilot-app/manifest.gen.ts create mode 100644 decopilot-app/mod.ts create mode 100644 decopilot-app/preview/Preview.tsx create mode 100644 decopilot-app/types.ts create mode 100644 decopilot-app/utils/assembleComplexPrompt.ts create mode 100644 decopilot-app/utils/handleAttachments.ts create mode 100644 decopilot-app/utils/llmCaller.ts create mode 100644 emailjs/README.md create mode 100644 emailjs/actions/send.ts create mode 100644 emailjs/logo.png create mode 100644 emailjs/manifest.gen.ts create mode 100644 emailjs/mod.ts create mode 100644 emailjs/utils/client.ts create mode 100644 emailjs/utils/types.ts create mode 100644 files/loaders/app.ts create mode 100644 files/manifest.gen.ts create mode 100644 files/mod.ts create mode 100644 files/sdk.ts create mode 100644 htmx/README.md create mode 100644 htmx/logo.png create mode 100644 htmx/manifest.gen.ts create mode 100644 htmx/mod.ts create mode 100644 htmx/sections/Deferred.tsx create mode 100644 htmx/sections/htmx.tsx create mode 100644 implementation/README.md create mode 100644 implementation/manifest.gen.ts create mode 100644 implementation/mod.ts delete mode 100644 import_map.json create mode 100644 konfidency/README.md create mode 100644 konfidency/loaders/productDetailsPage.ts create mode 100644 konfidency/manifest.gen.ts create mode 100644 konfidency/mod.ts create mode 100644 konfidency/utils/client.ts create mode 100644 konfidency/utils/transform.ts create mode 100644 konfidency/utils/types.ts create mode 100644 linx-impulse/actions/analytics/sendEvent.ts create mode 100644 linx-impulse/loaders/config.ts create mode 100644 linx-impulse/loaders/products/hotsite.ts create mode 100644 linx-impulse/loaders/products/linxEngage.ts create mode 100644 linx-impulse/loaders/products/productList.ts create mode 100644 linx-impulse/loaders/products/productListingPage.ts create mode 100644 linx-impulse/loaders/products/productLists.ts create mode 100644 linx-impulse/loaders/products/quickFilters.ts create mode 100644 linx-impulse/loaders/products/suggestions.ts create mode 100644 linx-impulse/loaders/search/autocomplete.ts create mode 100644 linx-impulse/loaders/search/popular.ts create mode 100644 linx-impulse/loaders/search/products.ts create mode 100644 linx-impulse/logo.png create mode 100644 linx-impulse/manifest.gen.ts create mode 100644 linx-impulse/middleware.ts create mode 100644 linx-impulse/mod.ts create mode 100644 linx-impulse/runtime.ts create mode 100644 linx-impulse/sections/Analytics/LinxImpulsePageView.tsx create mode 100644 linx-impulse/sections/Script/LinxImpulseScript.tsx create mode 100644 linx-impulse/utils/chaordic.ts create mode 100644 linx-impulse/utils/client.ts create mode 100644 linx-impulse/utils/constants.ts create mode 100644 linx-impulse/utils/deviceId.ts create mode 100644 linx-impulse/utils/events.ts create mode 100644 linx-impulse/utils/source.ts create mode 100644 linx-impulse/utils/transform.ts create mode 100644 linx-impulse/utils/types/analytics.ts create mode 100644 linx-impulse/utils/types/chaordic.ts create mode 100644 linx-impulse/utils/types/impulse.ts create mode 100644 linx-impulse/utils/types/linx.ts create mode 100644 linx-impulse/utils/types/search.ts create mode 100644 linx/README.md create mode 100644 linx/actions/auction/addBid.ts create mode 100644 linx/actions/cart/addCoupon.ts create mode 100644 linx/actions/cart/addItem.ts create mode 100644 linx/actions/cart/simulate.ts create mode 100644 linx/actions/cart/updateItem.ts create mode 100644 linx/actions/checkout/redirect.ts create mode 100644 linx/actions/login.ts create mode 100644 linx/actions/newsletter/subscribe.ts create mode 100644 linx/actions/wishlist/addItem.ts create mode 100644 linx/actions/wishlist/addWishlist.ts create mode 100644 linx/actions/wishlist/removeItem.ts create mode 100644 linx/actions/wishlist/removeWishlist.ts create mode 100644 linx/actions/wishlist/shareWishlist.ts create mode 100644 linx/actions/wishlist/updateWishlist.ts create mode 100644 linx/handlers/sitemap.ts create mode 100644 linx/hooks/context.ts create mode 100644 linx/hooks/useCart.ts create mode 100644 linx/hooks/useUser.ts create mode 100644 linx/hooks/useWishlist.ts create mode 100644 linx/loaders/auction/ListBids.ts create mode 100644 linx/loaders/auction/detailsPage.ts create mode 100644 linx/loaders/auction/list.ts create mode 100644 linx/loaders/cart.ts create mode 100644 linx/loaders/page.ts create mode 100644 linx/loaders/pages.ts create mode 100644 linx/loaders/path.ts create mode 100644 linx/loaders/product/associations.ts create mode 100644 linx/loaders/product/byId.ts create mode 100644 linx/loaders/product/detailsPage.ts create mode 100644 linx/loaders/product/list.ts create mode 100644 linx/loaders/product/listingPage.ts create mode 100644 linx/loaders/product/suggestions.ts create mode 100644 linx/loaders/proxy.ts create mode 100644 linx/loaders/user.ts create mode 100644 linx/loaders/widget.ts create mode 100644 linx/loaders/wishlist/search.ts create mode 100644 linx/logo.png create mode 100644 linx/manifest.gen.ts create mode 100644 linx/mod.ts create mode 100644 linx/runtime.ts create mode 100644 linx/utils/client.ts create mode 100644 linx/utils/headers.ts create mode 100644 linx/utils/layer.ts create mode 100644 linx/utils/paths.ts create mode 100644 linx/utils/transform.ts create mode 100644 linx/utils/types/ListBidsJSON.ts create mode 100644 linx/utils/types/associationsJSON.ts create mode 100644 linx/utils/types/auction.ts create mode 100644 linx/utils/types/auctionDetailJSON.ts create mode 100644 linx/utils/types/auctionJSON.ts create mode 100644 linx/utils/types/availableOptions.ts create mode 100644 linx/utils/types/basket.ts create mode 100644 linx/utils/types/basketJSON.ts create mode 100644 linx/utils/types/gridProductsJSON.ts create mode 100644 linx/utils/types/installments.ts create mode 100644 linx/utils/types/login.ts create mode 100644 linx/utils/types/newsletterJSON.ts create mode 100644 linx/utils/types/productByIdJSON.ts create mode 100644 linx/utils/types/productJSON.ts create mode 100644 linx/utils/types/productList.ts create mode 100644 linx/utils/types/shared.ts create mode 100644 linx/utils/types/suggestionsJSON.ts create mode 100644 linx/utils/types/userJSON.ts create mode 100644 linx/utils/types/wishlistJSON.ts create mode 100644 mailchimp/README.md create mode 100644 mailchimp/actions/subscribe.ts create mode 100644 mailchimp/actions/unsubscribe.ts create mode 100644 mailchimp/loaders/listMember.ts create mode 100644 mailchimp/loaders/lists.ts create mode 100644 mailchimp/loaders/options/lists.ts create mode 100644 mailchimp/logo.png create mode 100644 mailchimp/manifest.gen.ts create mode 100644 mailchimp/mod.ts create mode 100644 mailchimp/utils/client.ts create mode 100644 mailchimp/utils/transform.ts create mode 100644 mailchimp/utils/types.ts create mode 100644 nuvemshop/README.MD create mode 100644 nuvemshop/actions/cart/addItems.ts create mode 100644 nuvemshop/actions/cart/updateItems.ts create mode 100644 nuvemshop/handlers/sitemap.ts create mode 100644 nuvemshop/hooks/context.ts create mode 100644 nuvemshop/hooks/useCart.ts create mode 100644 nuvemshop/loaders/cart.ts create mode 100644 nuvemshop/loaders/productDetailsPage.ts create mode 100644 nuvemshop/loaders/productList.ts create mode 100644 nuvemshop/loaders/productListingPage.ts create mode 100644 nuvemshop/loaders/proxy.ts create mode 100644 nuvemshop/logo.png create mode 100644 nuvemshop/manifest.gen.ts create mode 100644 nuvemshop/mod.ts create mode 100644 nuvemshop/preview/index.tsx create mode 100644 nuvemshop/runtime.ts create mode 100644 nuvemshop/utils/cart.ts create mode 100644 nuvemshop/utils/client.ts create mode 100644 nuvemshop/utils/transform.ts create mode 100644 nuvemshop/utils/types.ts create mode 100644 openai/deps.ts create mode 100644 openai/loaders/vision.ts create mode 100644 openai/manifest.gen.ts create mode 100644 openai/mod.ts create mode 100644 posthog/components/PostHog.tsx create mode 100644 posthog/manifest.gen.ts create mode 100644 posthog/mod.ts create mode 100644 posthog/sections/Analytics/PostHog.tsx create mode 100644 power-reviews/actions/submitReview.ts create mode 100644 power-reviews/loaders/productDetailsPage.ts create mode 100644 power-reviews/loaders/productList.ts create mode 100644 power-reviews/loaders/productListingPage.ts create mode 100644 power-reviews/loaders/review.ts create mode 100644 power-reviews/loaders/reviewForm.ts create mode 100644 power-reviews/logo.png create mode 100644 power-reviews/manifest.gen.ts create mode 100644 power-reviews/mod.ts create mode 100644 power-reviews/readme.md create mode 100644 power-reviews/sections/Question.tsx create mode 100644 power-reviews/sections/WriteReviewForm.tsx create mode 100644 power-reviews/utils/client.ts create mode 100644 power-reviews/utils/tranform.ts create mode 100644 power-reviews/utils/types.ts create mode 100644 ra-trustvox/components/TrustvoxCertificateFixed.tsx create mode 100644 ra-trustvox/components/TrustvoxProductDetailsRate.tsx create mode 100644 ra-trustvox/components/TrustvoxShelfRate.tsx create mode 100644 ra-trustvox/manifest.gen.ts create mode 100644 ra-trustvox/mod.ts create mode 100644 ra-trustvox/ra-trustvox.png create mode 100644 ra-trustvox/sections/TrustvoxCertificate.tsx create mode 100644 ra-trustvox/sections/TrustvoxProductReviews.tsx create mode 100644 ra-trustvox/sections/TrustvoxRateConfig.tsx create mode 100644 ra-trustvox/sections/TrustvoxStoreReviewsCarousel.tsx create mode 100644 records/deps.ts create mode 100644 records/loaders/drizzle.ts create mode 100644 records/loaders/sqlClient.ts create mode 100644 records/logo.png create mode 100644 records/manifest.gen.ts create mode 100644 records/mod.ts create mode 100644 records/scripts/checkDbCredential.ts create mode 100644 records/scripts/pullProd.ts create mode 100644 records/utils.ts create mode 100644 resend/README.md create mode 100644 resend/actions/emails/send.ts create mode 100644 resend/logo.png create mode 100644 resend/mailExamples/StripeWelcomeEmail.tsx create mode 100644 resend/manifest.gen.ts create mode 100644 resend/mod.ts create mode 100644 resend/utils/client.ts create mode 100644 resend/utils/reactEmail.ts create mode 100644 resend/utils/types.ts create mode 100644 sap/README.md create mode 100644 sap/loaders/categories/tree.ts create mode 100644 sap/loaders/product/ProductList.ts create mode 100644 sap/loaders/product/productDetailsPage.ts create mode 100644 sap/loaders/product/productListingPage.ts create mode 100644 sap/manifest.gen.ts create mode 100644 sap/mod.ts create mode 100644 sap/runtime.ts create mode 100644 sap/utils/client/client.ts create mode 100644 sap/utils/constants.ts create mode 100644 sap/utils/transform.ts create mode 100644 sap/utils/types.ts create mode 100644 scripts/new.ts create mode 100644 scripts/start.ts create mode 100644 shopify/README.md create mode 100644 shopify/actions/cart/addItems.ts create mode 100644 shopify/actions/cart/updateCoupons.ts create mode 100644 shopify/actions/cart/updateItems.ts create mode 100644 shopify/actions/order/draftOrderCalculate.ts create mode 100644 shopify/actions/user/signIn.ts create mode 100644 shopify/handlers/sitemap.ts create mode 100644 shopify/hooks/context.ts create mode 100644 shopify/hooks/useCart.ts create mode 100644 shopify/hooks/useUser.ts create mode 100644 shopify/loaders/ProductDetailsPage.ts create mode 100644 shopify/loaders/ProductList.ts create mode 100644 shopify/loaders/ProductListingPage.ts create mode 100644 shopify/loaders/RelatedProducts.ts create mode 100644 shopify/loaders/cart.ts create mode 100644 shopify/loaders/proxy.ts create mode 100644 shopify/loaders/user.ts create mode 100644 shopify/logo.png create mode 100644 shopify/manifest.gen.ts create mode 100644 shopify/mod.ts create mode 100644 shopify/runtime.ts create mode 100644 shopify/utils/admin/admin.graphql.gen.ts create mode 100644 shopify/utils/admin/admin.graphql.json create mode 100644 shopify/utils/admin/queries.ts create mode 100644 shopify/utils/cart.ts create mode 100644 shopify/utils/enums.ts create mode 100644 shopify/utils/password.ts create mode 100644 shopify/utils/storefront/queries.ts create mode 100644 shopify/utils/storefront/storefront.graphql.gen.ts create mode 100644 shopify/utils/storefront/storefront.graphql.json create mode 100644 shopify/utils/transform.ts create mode 100644 shopify/utils/types.ts create mode 100644 shopify/utils/user.ts create mode 100644 shopify/utils/utils.ts create mode 100644 smarthint/README.md create mode 100644 smarthint/actions/click.ts create mode 100644 smarthint/actions/pageview.ts create mode 100644 smarthint/components/Click.tsx create mode 100644 smarthint/hooks/useAutocomplete.ts create mode 100644 smarthint/loaders/PLPBanners.ts create mode 100644 smarthint/loaders/autocomplete.ts create mode 100644 smarthint/loaders/banners.ts create mode 100644 smarthint/loaders/productListingPage.ts create mode 100644 smarthint/loaders/recommendations.ts create mode 100644 smarthint/logo.png create mode 100644 smarthint/manifest.gen.ts create mode 100644 smarthint/mod.ts create mode 100644 smarthint/runtime.ts create mode 100644 smarthint/sections/Analytics/SmarthintTracking.tsx create mode 100644 smarthint/utils/getSession.ts create mode 100644 smarthint/utils/openapi/smarthint.openapi.gen.ts create mode 100644 smarthint/utils/openapi/smarthint.openapi.json create mode 100644 smarthint/utils/sortPagesPattern.ts create mode 100644 smarthint/utils/transform.ts create mode 100644 smarthint/utils/typings.ts create mode 100644 sourei/README.md create mode 100644 sourei/logo.png create mode 100644 sourei/logo.svg create mode 100644 sourei/manifest.gen.ts create mode 100644 sourei/mod.ts create mode 100644 sourei/sections/Analytics/Sourei.tsx create mode 100644 typesense/README.md create mode 100644 typesense/actions/index/product.ts create mode 100644 typesense/fly.toml create mode 100644 typesense/loaders/product/list.ts create mode 100644 typesense/loaders/product/listingPage.ts create mode 100644 typesense/logo.png create mode 100644 typesense/manifest.gen.ts create mode 100644 typesense/mod.ts create mode 100644 typesense/utils/highlight.ts create mode 100644 typesense/utils/once.ts create mode 100644 typesense/utils/product.ts create mode 100644 typesense/workflows/index/product.ts delete mode 100644 utils/HttpError.ts create mode 100644 utils/capitalize.ts create mode 100644 utils/components/Slider.tsx create mode 100644 utils/components/SliderJS.tsx create mode 100644 utils/cookie.ts create mode 100644 utils/dataURI.ts create mode 100644 utils/defaultErrorPage.tsx create mode 100644 utils/deferred.ts create mode 100644 utils/framework.tsx create mode 100644 utils/graphql.ts create mode 100644 utils/http.ts create mode 100644 utils/lru.ts create mode 100644 utils/normalize.ts create mode 100644 utils/preview.tsx create mode 100644 utils/shortHash.ts create mode 100644 utils/weakcache.ts create mode 100644 verified-reviews/loaders/productDetailsPage.ts create mode 100644 verified-reviews/loaders/productList.ts create mode 100644 verified-reviews/loaders/productListingPage.ts create mode 100644 verified-reviews/loaders/storeReview.ts create mode 100644 verified-reviews/logo.png create mode 100644 verified-reviews/manifest.gen.ts create mode 100644 verified-reviews/mod.ts create mode 100644 verified-reviews/utils/client.ts create mode 100644 verified-reviews/utils/transform.ts create mode 100644 verified-reviews/utils/types.ts create mode 100644 vnda/README.md create mode 100644 vnda/actions/cart/addItems.ts delete mode 100644 vnda/actions/cart/setShippingAddress.ts create mode 100644 vnda/actions/cart/simulation.ts create mode 100644 vnda/actions/cart/updateCart.ts delete mode 100644 vnda/actions/cart/updateCoupon.ts create mode 100644 vnda/actions/notifyme.ts delete mode 100644 vnda/doccache.zst create mode 100644 vnda/handlers/sitemap.ts create mode 100644 vnda/loaders/extensions/price/list.ts create mode 100644 vnda/loaders/extensions/price/listingPage.ts create mode 100644 vnda/loaders/productDetailsPageVideo.ts create mode 100644 vnda/loaders/proxy.ts create mode 100644 vnda/logo.png create mode 100644 vnda/middleware.ts create mode 100644 vnda/utils/cart.ts create mode 100644 vnda/utils/constants.ts create mode 100644 vnda/utils/openapi/vnda.openapi.gen.ts create mode 100644 vnda/utils/openapi/vnda.openapi.json delete mode 100644 vnda/utils/paths.ts delete mode 100644 vnda/utils/queryBuilder.ts create mode 100644 vnda/utils/segment.ts create mode 100644 vtex/README.md create mode 100644 vtex/actions/address/createAddress.ts create mode 100644 vtex/actions/address/deleteAddress.ts create mode 100644 vtex/actions/address/updateAddress.ts create mode 100644 vtex/actions/analytics/sendEvent.ts create mode 100644 vtex/actions/cart/addItems.ts create mode 100644 vtex/actions/cart/addOfferings.ts create mode 100644 vtex/actions/cart/clearOrderformMessages.ts create mode 100644 vtex/actions/cart/getInstallment.ts create mode 100644 vtex/actions/cart/removeItemAttachment.ts create mode 100644 vtex/actions/cart/removeItems.ts create mode 100644 vtex/actions/cart/removeOffering.ts create mode 100644 vtex/actions/cart/simulation.ts create mode 100644 vtex/actions/cart/updateAttachment.ts create mode 100644 vtex/actions/cart/updateCoupons.ts create mode 100644 vtex/actions/cart/updateGifts.ts create mode 100644 vtex/actions/cart/updateItemAttachment.ts create mode 100644 vtex/actions/cart/updateItemPrice.ts create mode 100644 vtex/actions/cart/updateItems.ts create mode 100644 vtex/actions/cart/updateProfile.ts create mode 100644 vtex/actions/cart/updateUser.ts create mode 100644 vtex/actions/masterdata/createDocument.ts create mode 100644 vtex/actions/newsletter/subscribe.ts create mode 100644 vtex/actions/notifyme.ts create mode 100644 vtex/actions/payments/delete.ts create mode 100644 vtex/actions/profile/newsletterProfile.ts create mode 100644 vtex/actions/profile/updateProfile.ts create mode 100644 vtex/actions/review/submit.ts create mode 100644 vtex/actions/sessions/delete.ts create mode 100644 vtex/actions/trigger.ts create mode 100644 vtex/actions/wishlist/addItem.ts create mode 100644 vtex/actions/wishlist/removeItem.ts create mode 100644 vtex/components/VTEXPortalDataLayerCompatibility.tsx delete mode 100644 vtex/doccache.zst create mode 100644 vtex/handlers/sitemap.ts create mode 100644 vtex/hooks/context.ts create mode 100644 vtex/hooks/useAutocomplete.ts create mode 100644 vtex/hooks/useCart.ts create mode 100644 vtex/hooks/useUser.ts create mode 100644 vtex/hooks/useWishlist.ts create mode 100644 vtex/loaders/address/getAddressByZIP.ts create mode 100644 vtex/loaders/address/list.ts create mode 100644 vtex/loaders/cart.ts create mode 100644 vtex/loaders/categories/tree.ts create mode 100644 vtex/loaders/collections/list.ts create mode 100644 vtex/loaders/config.ts create mode 100644 vtex/loaders/intelligentSearch/productDetailsPage.ts create mode 100644 vtex/loaders/intelligentSearch/productList.ts create mode 100644 vtex/loaders/intelligentSearch/productListingPage.ts create mode 100644 vtex/loaders/intelligentSearch/productSearchValidator.ts create mode 100644 vtex/loaders/intelligentSearch/suggestions.ts create mode 100644 vtex/loaders/intelligentSearch/topsearches.ts create mode 100644 vtex/loaders/legacy/brands.ts create mode 100644 vtex/loaders/legacy/pageType.ts create mode 100644 vtex/loaders/legacy/productDetailsPage.ts create mode 100644 vtex/loaders/legacy/productList.ts create mode 100644 vtex/loaders/legacy/productListingPage.ts create mode 100644 vtex/loaders/legacy/relatedProductsLoader.ts create mode 100644 vtex/loaders/legacy/suggestions.ts create mode 100644 vtex/loaders/logistics/listPickupPoints.ts create mode 100644 vtex/loaders/logistics/listPickupPointsByLocation.ts create mode 100644 vtex/loaders/masterdata/searchDocuments.ts create mode 100644 vtex/loaders/navbar.ts create mode 100644 vtex/loaders/options/productIdByTerm.ts create mode 100644 vtex/loaders/orders/list.ts create mode 100644 vtex/loaders/orders/order.ts create mode 100644 vtex/loaders/paths/PDPDefaultPath.ts create mode 100644 vtex/loaders/paths/PLPDefaultPath.ts create mode 100644 vtex/loaders/payments/info.ts create mode 100644 vtex/loaders/payments/userPayments.ts create mode 100644 vtex/loaders/product/extend.ts create mode 100644 vtex/loaders/product/extensions/detailsPage.ts create mode 100644 vtex/loaders/product/extensions/list.ts create mode 100644 vtex/loaders/product/extensions/listingPage.ts create mode 100644 vtex/loaders/product/extensions/suggestions.ts create mode 100644 vtex/loaders/product/wishlist.ts create mode 100644 vtex/loaders/profile/passwordLastUpdate.ts create mode 100644 vtex/loaders/proxy.ts create mode 100644 vtex/loaders/sessions/info.ts create mode 100644 vtex/loaders/user.ts create mode 100644 vtex/loaders/wishlist.ts create mode 100644 vtex/loaders/workflow/product.ts create mode 100644 vtex/loaders/workflow/products.ts create mode 100644 vtex/logo.png create mode 100644 vtex/middleware.ts create mode 100644 vtex/preview/Preview.tsx create mode 100644 vtex/runtime.ts create mode 100644 vtex/sections/Analytics/Vtex.tsx create mode 100644 vtex/utils/batch.ts create mode 100644 vtex/utils/cacheBySegment.ts create mode 100644 vtex/utils/client.ts create mode 100644 vtex/utils/cookies.ts create mode 100644 vtex/utils/extensions/simulation.ts create mode 100644 vtex/utils/fetchVTEX.ts create mode 100644 vtex/utils/intelligentSearch.ts create mode 100644 vtex/utils/legacy.ts create mode 100644 vtex/utils/openapi/api.openapi.gen.ts create mode 100644 vtex/utils/openapi/api.openapi.json create mode 100644 vtex/utils/openapi/my.openapi.gen.ts create mode 100644 vtex/utils/openapi/my.openapi.json create mode 100644 vtex/utils/openapi/vcs.openapi.gen.ts create mode 100644 vtex/utils/openapi/vcs.openapi.json create mode 100644 vtex/utils/orderForm.ts create mode 100644 vtex/utils/resourceRange.ts create mode 100644 vtex/utils/segment.ts create mode 100644 vtex/utils/similars.ts create mode 100644 vtex/utils/slugify.ts create mode 100644 vtex/utils/transform.ts create mode 100644 vtex/utils/types.ts create mode 100644 vtex/utils/vtexId.ts create mode 100644 vtex/workflows/events.ts create mode 100644 vtex/workflows/product/index.ts create mode 100644 wake/README.md create mode 100644 wake/actions/cart/addCoupon.ts create mode 100644 wake/actions/cart/addItem.ts create mode 100644 wake/actions/cart/addItems.ts create mode 100644 wake/actions/cart/addKit.ts create mode 100644 wake/actions/cart/partnerAssociate.ts create mode 100644 wake/actions/cart/partnerDisassociate.ts create mode 100644 wake/actions/cart/removeCoupon.ts create mode 100644 wake/actions/cart/removeKit.ts create mode 100644 wake/actions/cart/updateItemQuantity.ts create mode 100644 wake/actions/newsletter/register.ts create mode 100644 wake/actions/notifyme.ts create mode 100644 wake/actions/review/create.ts create mode 100644 wake/actions/shippingSimulation.ts create mode 100644 wake/actions/submmitForm.ts create mode 100644 wake/actions/wishlist/addProduct.ts create mode 100644 wake/actions/wishlist/removeProduct.ts create mode 100644 wake/handlers/sitemap.ts create mode 100644 wake/hooks/context.ts create mode 100644 wake/hooks/useCart.ts create mode 100644 wake/hooks/useUser.ts create mode 100644 wake/hooks/useWishlist.ts create mode 100644 wake/loaders/cart.ts create mode 100644 wake/loaders/partners.ts create mode 100644 wake/loaders/productDetailsPage.ts create mode 100644 wake/loaders/productList.ts create mode 100644 wake/loaders/productListingPage.ts create mode 100644 wake/loaders/proxy.ts create mode 100644 wake/loaders/recommendations.ts create mode 100644 wake/loaders/shop.ts create mode 100644 wake/loaders/suggestion.ts create mode 100644 wake/loaders/user.ts create mode 100644 wake/loaders/wishlist.ts create mode 100644 wake/logo.png create mode 100644 wake/manifest.gen.ts create mode 100644 wake/mod.ts create mode 100644 wake/runtime.ts create mode 100644 wake/utils/authenticate.ts create mode 100644 wake/utils/cart.ts create mode 100644 wake/utils/client.ts create mode 100644 wake/utils/getVariations.ts create mode 100644 wake/utils/graphql/queries.ts create mode 100644 wake/utils/graphql/storefront.graphql.gen.ts create mode 100644 wake/utils/graphql/storefront.graphql.json create mode 100644 wake/utils/openapi/wake.openapi.gen.ts create mode 100644 wake/utils/openapi/wake.openapi.json create mode 100644 wake/utils/parseHeaders.ts create mode 100644 wake/utils/transform.ts create mode 100644 wake/utils/user.ts create mode 100644 wap/actions/cart/addItems.ts create mode 100644 wap/actions/cart/removeItem.ts create mode 100644 wap/actions/cart/updateCoupon.ts create mode 100644 wap/actions/cart/updateItem.ts create mode 100644 wap/actions/evaluations/product.ts create mode 100644 wap/actions/forms/contact.ts create mode 100644 wap/actions/newsletter/associates.ts create mode 100644 wap/actions/newsletter/register.ts create mode 100644 wap/actions/product/solicitation.ts create mode 100644 wap/actions/question/answer.ts create mode 100644 wap/actions/question/question.ts create mode 100644 wap/actions/shipment/product.ts create mode 100644 wap/actions/wishlist/addItem.ts create mode 100644 wap/actions/wishlist/removeItem.ts create mode 100644 wap/hooks/context.ts create mode 100644 wap/hooks/useCart.ts create mode 100644 wap/hooks/useUser.ts create mode 100644 wap/hooks/useWishlist.ts create mode 100644 wap/loaders/cart.ts create mode 100644 wap/loaders/productDetailsPage.ts create mode 100644 wap/loaders/productList.ts create mode 100644 wap/loaders/productListIndicated.ts create mode 100644 wap/loaders/productListLastSeen.ts create mode 100644 wap/loaders/productListingPage.ts create mode 100644 wap/loaders/proxy.ts create mode 100644 wap/loaders/suggestions.ts create mode 100644 wap/loaders/user.ts create mode 100644 wap/loaders/wishlist.ts create mode 100644 wap/manifest.gen.ts create mode 100644 wap/mod.ts create mode 100644 wap/runtime.ts create mode 100644 wap/utils/cart.ts create mode 100644 wap/utils/openapi/api.openapi.gen.ts create mode 100644 wap/utils/openapi/api.openapi.json create mode 100644 wap/utils/transform.ts create mode 100644 wap/utils/type.ts create mode 100644 weather/loaders/temperature.ts create mode 100644 weather/logo.png create mode 100644 weather/manifest.gen.ts create mode 100644 weather/matchers/temperature.ts create mode 100644 weather/mod.ts create mode 100644 weather/sections/WhatsTheTemperature.tsx create mode 100644 website/Preview.tsx create mode 100644 website/components/Analytics.tsx create mode 100644 website/components/Clickhouse.tsx create mode 100644 website/components/Events.tsx create mode 100644 website/components/PoweredByDeco.tsx create mode 100644 website/components/Seo.tsx create mode 100644 website/components/Theme.tsx delete mode 100644 website/components/_Analytics.tsx rename website/{seo/components => components/_seo}/Discord.tsx (82%) create mode 100644 website/components/_seo/Facebook.tsx create mode 100644 website/components/_seo/Google.tsx create mode 100644 website/components/_seo/Icons.tsx create mode 100644 website/components/_seo/LinkedIn.tsx create mode 100644 website/components/_seo/Preview.tsx rename website/{seo/components => components/_seo}/Slack.tsx (68%) create mode 100644 website/components/_seo/Telegram.tsx rename website/{seo/components => components/_seo}/Twitter.tsx (50%) create mode 100644 website/components/_seo/WhatsApp.tsx rename website/{seo/components => components/_seo}/helpers/textShortner.tsx (100%) rename website/{seo/components => components/_seo}/instructions.json (100%) delete mode 100644 website/doccache.zst create mode 100644 website/flags/multivariate/image.ts create mode 100644 website/flags/multivariate/message.ts create mode 100644 website/flags/multivariate/page.ts create mode 100644 website/flags/multivariate/section.ts create mode 100644 website/loaders/extension.ts create mode 100644 website/loaders/fonts/googleFonts.ts create mode 100644 website/loaders/fonts/local.ts create mode 100644 website/loaders/options/routes.ts create mode 100644 website/loaders/options/urlParams.ts create mode 100644 website/loaders/pages.ts create mode 100644 website/loaders/redirect.ts create mode 100644 website/loaders/redirects.ts create mode 100644 website/loaders/secretString.ts create mode 100644 website/logo.png create mode 100644 website/matchers/cookie.ts create mode 100644 website/matchers/negate.ts create mode 100644 website/matchers/never.ts create mode 100644 website/matchers/pathname.ts create mode 100644 website/matchers/queryString.ts delete mode 100644 website/pages/LivePage.tsx create mode 100644 website/pages/Page.tsx create mode 100644 website/sections/Analytics/Analytics.tsx create mode 100644 website/sections/Rendering/Deferred.tsx create mode 100644 website/sections/Rendering/Lazy.tsx create mode 100644 website/sections/Rendering/SingleDeferred.tsx create mode 100644 website/sections/Seo/Seo.tsx create mode 100644 website/sections/Seo/SeoV2.tsx delete mode 100644 website/seo/Head.tsx delete mode 100644 website/seo/Metatags.tsx delete mode 100644 website/seo/ScriptLDJson.tsx delete mode 100644 website/seo/components/Facebook.tsx delete mode 100644 website/seo/components/Google.tsx delete mode 100644 website/seo/components/LinkedIn.tsx delete mode 100644 website/seo/components/Preview.tsx delete mode 100644 website/seo/components/PreviewItem.tsx delete mode 100644 website/seo/components/Telegram.tsx delete mode 100644 website/seo/components/WhatsApp.tsx delete mode 100644 website/seo/components/fragments/Title.tsx delete mode 100644 website/seo/types.ts delete mode 100644 website/seo/utils.ts create mode 100644 website/types.ts create mode 100644 website/utils/html.ts create mode 100644 website/utils/location.ts create mode 100644 website/utils/multivariate.ts create mode 100644 website/utils/unhandledRejection.ts delete mode 100644 workflows/doccache.zst create mode 100644 workflows/logo.png create mode 100644 workflows/utils/awaiters.ts diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5fcf9011bceb5785eed184c3925f56f0adf71301 GIT binary patch literal 6148 zcmeHKOG-mQ5UkcL0wQGTaxUP)8w??yzze8(3PRuoH2b&mTprEp&%*F@7dN3AdTOSp zYlf-C>(>BmeHiY56@Vq(5uZNH&G+4Bc2N-{(s{-nZ`fhP2X1HCzXzOqjR7y%lJ}GU z#MSOI>A{wLJCNM^HRXS4~_2F3#Y{Rba03k zfVf~djPvLvh|L4UUN|K(LbIe2lWNssSkf79mDdZW#H7Qj`LMd#szb53o#(emhxJ5_ zQa}nED{z_1mDm4!`XBxOF-a>aAO+4!0b6VyHfuhqYU}KAUTYiuk?uKPbT`g}!Xe5r kG0HI)UXE`fDf61ox!((?#Go@CbfSI+To;)X_-_Ti0ov3SZU6uP literal 0 HcmV?d00001 diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 000000000..5ef78cb3f --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,45 @@ +--- +name: Issue Report +about: Report a bug, suggest an enhancement, or ask a question +title: "" +labels: "" +assignees: "" +--- + +## Issue Type + +Please inform the type(s) of issue(s) you are reporting: + +- Bug Report +- Feature Request +- Discussion +- Question + +## Description + +Please provide a clear and concise description of the issue or enhancement. +Include any relevant information that could help reproduce the issue or +understand the request. + +## Steps to Reproduce (for bugs) + +1. Step one +2. Step two +3. ... + +## Expected Behavior + +Describe what you expected to happen. + +## Actual Behavior + +Describe what actually happened. + +## Additional Context + +Add any other context about the issue here, including screenshots, logs, or +other supporting information. + +--- + +Thank you for taking the time to report this issue! diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..988cd8c60 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ + +## What is this Contribution About? + +Please provide a brief description of the changes or enhancements you are proposing in this pull request. + +## Issue Link + +Please link to the relevant issue that this pull request addresses: + +- Issue: [#ISSUE_NUMBER](link_to_issue) + +## Loom Video + +> Record a quick screencast describing your changes to help the team understand and review your contribution. This will greatly assist in the review process. + +## Demonstration Link + +> Provide a link to a branch or environment where this pull request can be tested and seen in action. diff --git a/.github/release.yaml b/.github/release.yaml new file mode 100644 index 000000000..c7ff97780 --- /dev/null +++ b/.github/release.yaml @@ -0,0 +1,20 @@ +changelog: + exclude: + labels: + - ignore-for-release + categories: + - title: Breaking Changes 🚨 + labels: + - breaking-change + - title: Exciting New Features 🎉 + labels: + - enhancement + - title: Preview features 🍪 + labels: + - preview-feature + - title: Bugs fixed 🐛 + labels: + - bug + - title: Other Changes + labels: + - "*" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 000000000..aa1d2bb32 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,54 @@ +name: ci + +on: + push: + branches: + - main + tags: + - v* + pull_request: + branches: + - main +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + name: "Bundle & Check Apps" + steps: + - uses: actions/checkout@v3 + - name: cache deno installation and deno.land dependencies + uses: actions/cache@v2 + with: + key: ${{ runner.os }}-deno-${{ hashFiles('**/*') }} + restore-keys: ${{ runner.os }}-deno- + path: | + /home/runner/.deno + /home/runner/.cache/deno/deps/https/deno.land + - uses: denoland/setup-deno@v1 + with: + deno-version: v1.x + - name: Bundle Apps + run: deno run -A --lock=deno.lock --lock-write --reload scripts/start.ts + + - name: Check + run: deno task check + + - name: Check if there are changes on ${{ matrix.os }} + id: changes + shell: bash + run: | + git status --porcelain + if [[ $(git status --porcelain | wc -c) -eq 0 ]]; then + echo "uncommitted changes detected" + exit 1 + fi + + - name: Test + continue-on-error: true + run: deno test --lock=deno.lock --lock-write -A . + + - name: Benchmark + continue-on-error: true + run: deno bench --lock=deno.lock --lock-write -A . diff --git a/.github/workflows/issues.yaml b/.github/workflows/issues.yaml new file mode 100644 index 000000000..d5f99f015 --- /dev/null +++ b/.github/workflows/issues.yaml @@ -0,0 +1,18 @@ +name: Label issues +on: + issues: + types: + - reopened + - opened +jobs: + label_issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - run: gh issue edit "$NUMBER" --add-label "$LABELS" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + NUMBER: ${{ github.event.issue.number }} + LABELS: triage diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 000000000..c7e75e636 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,45 @@ +# https://docs.github.com/en/actions + +name: "Release" + +on: # yamllint disable-line rule:truthy + push: + tags: + - "**" + workflow_dispatch: # Allows manual dispatch with parameters + inputs: + tag_name: + description: "The tag to be published" + required: true +permissions: write-all +jobs: + release: + permissions: write-all + name: "Release" + runs-on: "ubuntu-latest" + + steps: + - name: "Create release" + env: + RELEASE_TAG: ${{ github.event.inputs.tag_name || github.ref_name }} + uses: "actions/github-script@v6" + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + script: | + const tag = process.env.RELEASE_TAG; + try { + const response = await github.rest.repos.createRelease({ + draft: false, + generate_release_notes: true, + name: `Release ${tag}`, + owner: context.repo.owner, + prerelease: tag.includes("rc-") || tag.includes("preview") || tag.includes("beta") || tag.includes("alpha"), + repo: context.repo.repo, + tag_name: tag, + }); + + core.exportVariable('RELEASE_ID', response.data.id); + core.exportVariable('RELEASE_UPLOAD_URL', response.data.upload_url); + } catch (error) { + core.setFailed(error.message); + } diff --git a/.github/workflows/releaser.yaml b/.github/workflows/releaser.yaml new file mode 100644 index 000000000..f4fb8d2a0 --- /dev/null +++ b/.github/workflows/releaser.yaml @@ -0,0 +1,172 @@ +name: Release Tagging + +on: + pull_request_target: + types: [opened] + + push: + branches: + - main + +permissions: + contents: write # Necessary for accessing and modifying repository content + pull-requests: write # Necessary for interacting with pull requests + actions: write # Necessary for triggering other workflows + +jobs: + tag-discussion: + if: github.event_name == 'pull_request_target' && github.event.action == 'opened' + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.base.ref }} # Checkout the base branch (target repository) + repository: ${{ github.event.pull_request.base.repo.full_name }} # Checkout from the target repo + + - name: Calculate new versions + id: calculate_versions + run: | + git fetch --tags + LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1) + if [ -z "$LATEST_TAG" ]; then + LATEST_TAG="0.0.0" + fi + MAJOR=$(echo $LATEST_TAG | cut -d. -f1) + MINOR=$(echo $LATEST_TAG | cut -d. -f2) + PATCH=$(echo $LATEST_TAG | cut -d. -f3) + NEW_PATCH_VERSION="$MAJOR.$MINOR.$((PATCH + 1))" + NEW_MINOR_VERSION="$MAJOR.$((MINOR + 1)).0" + NEW_MAJOR_VERSION="$((MAJOR + 1)).0.0" + echo "patch_version=$NEW_PATCH_VERSION" >> $GITHUB_OUTPUT + echo "minor_version=$NEW_MINOR_VERSION" >> $GITHUB_OUTPUT + echo "major_version=$NEW_MAJOR_VERSION" >> $GITHUB_OUTPUT + + - name: Start Discussion for Tagging + uses: peter-evans/create-or-update-comment@v2 + id: comment + with: + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.pull_request.number }} + body: | + ## Tagging Options + Should a new tag be published when this PR is merged? + - 👍 for **Patch** ${{ steps.calculate_versions.outputs.patch_version }} update + - 🎉 for **Minor** ${{ steps.calculate_versions.outputs.minor_version }} update + - 🚀 for **Major** ${{ steps.calculate_versions.outputs.major_version }} update + + determine-tag: + if: github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Find the Merged Pull Request + id: find_pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + BASE_BRANCH="main" + RECENT_PR=$(gh pr list --state closed --base $BASE_BRANCH --json number,title,closedAt --jq '.[] | select(.closedAt >= "'$(date -u -d '1 minute ago' +%Y-%m-%dT%H:%M:%SZ)'") | {number, title}') + echo "RECENT_PR=$RECENT_PR" >> $GITHUB_ENV + echo "PR_NUMBER=$(echo $RECENT_PR | jq -r '.number')" >> $GITHUB_ENV + + - name: Fetch latest stable tag (excluding prerelease tags) + id: get_latest_tag + run: | + git fetch --tags + LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1) + if [ -z "$LATEST_TAG" ]; then + LATEST_TAG="0.0.0" + fi + echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT + + - name: Determine the next version based on comments + id: determine_version + if: env.PR_NUMBER != '' + env: + PR_NUMBER: ${{ env.PR_NUMBER }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + LATEST_TAG=${{ steps.get_latest_tag.outputs.latest_tag }} + MAJOR=$(echo $LATEST_TAG | cut -d. -f1) + MINOR=$(echo $LATEST_TAG | cut -d. -f2) + PATCH=$(echo $LATEST_TAG | cut -d. -f3) + + # Define allowed users as a JSON array + ALLOWED_USERS=$(cat MAINTAINERS.txt | jq -R -s -c 'split("\n")[:-1]') + echo "Maintainers list: $ALLOWED_USERS" + + # Fetch reactions and filter by allowed users + REACTION=$(gh api graphql -f query=' + query { + repository(owner:"${{ github.repository_owner }}", name:"${{ github.event.repository.name }}") { + pullRequest(number: '${PR_NUMBER}') { + comments(last: 100) { + nodes { + body + id + reactions(last: 100) { + nodes { + content + user { + login + } + } + } + } + } + } + } + }' | jq -r --argjson allowed_users "$ALLOWED_USERS" ' + .data.repository.pullRequest.comments.nodes[] | + select(.body | contains("## Tagging Options")) | + .reactions.nodes[] | + select(.user.login | IN($allowed_users[])) | + .content' + ) + # Print the reaction to debug + echo "Captured reaction: $REACTION" + + # Convert reaction to uppercase to handle any case inconsistencies + REACTION=$(echo "$REACTION" | tr '[:lower:]' '[:upper:]') + + # Determine the new tag version based on the allowed reactions + if [[ "$REACTION" == *"ROCKET"* ]]; then + NEW_TAG="$((MAJOR + 1)).0.0" + elif [[ "$REACTION" == *"HOORAY"* ]]; then + NEW_TAG="$MAJOR.$((MINOR + 1)).0" + elif [[ "$REACTION" == *"THUMBS_UP"* ]]; then # Ensure thumbs up reaction is correctly identified + NEW_TAG="$MAJOR.$MINOR.$((PATCH + 1))" + else + echo "No valid reactions found for version bump. Exiting." + exit 0 + fi + + + echo "new_version=$NEW_TAG" >> $GITHUB_OUTPUT + + - name: Update deno.json Version + if: steps.determine_version.outputs.new_version != '' + run: | + jq --arg new_version "${{ steps.determine_version.outputs.new_version }}" '.version = $new_version' deno.json > tmp.$$.json && mv tmp.$$.json deno.json + git config user.name "decobot" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add deno.json + git commit -m "Update version to ${{ steps.determine_version.outputs.new_version }}" + git push origin main + + - name: Create and Push Tag + if: steps.determine_version.outputs.new_version != '' + run: | + git tag ${{ steps.determine_version.outputs.new_version }} + git push origin ${{ steps.determine_version.outputs.new_version }} + + - name: Trigger Release Workflow + run: | + curl -X POST \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/vnd.github.everest-preview+json" \ + https://api.github.com/repos/${{ github.repository }}/actions/workflows/release.yaml/dispatches \ + -d '{"ref":"main", "inputs":{"tag_name":"${{ steps.determine_version.outputs.new_version }}"}}' diff --git a/.vscode/settings.json b/.vscode/settings.json index 1535e139e..e40716fdd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,4 +2,4 @@ "deno.enable": true, "deno.lint": true, "deno.unstable": true -} \ No newline at end of file +} diff --git a/CODE-OF-CONDUCT.md b/CODE-OF-CONDUCT.md new file mode 100644 index 000000000..e967f6cd7 --- /dev/null +++ b/CODE-OF-CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of +experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, or to ban temporarily or permanently any +contributor for other behaviors that they deem inappropriate, threatening, +offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at {{ email }}. All complaints will be +reviewed and investigated and will result in a response that is deemed necessary +and appropriate to the circumstances. The project team is obligated to maintain +confidentiality with regard to the reporter of an incident. Further details of +specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..5114d78e1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,88 @@ +# Contributing Guidelines + +Thank you for your interest in contributing to the `deco-cx/apps` repository! We +are excited to have community members like you involved in improving our +collection of powerful apps. This document outlines how you can contribute +effectively to the project. + +## How to Contribute + +### Issues + +When submitting an issue, please use one of the following types: + +- **Issue/Bug**: Report bugs or track existing issues. +- **Issue/Discussion**: Start a discussion to gather input on a topic before it + becomes a proposal. +- **Issue/Proposal**: Propose a new idea or functionality. This allows for + feedback before any code is written. +- **Issue/Question**: Ask for help or clarification on any topic related to the + project. + +### Before You File an Issue + +Before submitting an issue, ensure the following: + +1. **Correct Repository**: Verify that you are filing the issue in the correct + repository within the deco ecosystem. +2. **Existing Issues**: Search through [open issues](./issues) to check if the + issue has already been reported or the feature has already been requested. +3. **For Bugs**: + - Confirm that it’s not an environment-specific issue. Ensure all + prerequisites (e.g., dependencies, configurations) are met. + - Provide detailed logs, stack traces, or any other relevant data to help + diagnose the issue. +4. **For Proposals**: + - Discuss potential features in the appropriate issue to gather feedback + before coding. + +### Pull Requests + +We welcome contributions via pull requests (PRs). Follow this workflow to submit +your changes: + +1. **Issue Reference**: Ensure there is an issue raised that corresponds to your + PR. +2. **Fork and Branch**: Fork the repository and create a new branch for your + changes. +3. **Code Changes**: + - Include appropriate tests with your code changes. + - Run linters and format the code according to project standards: + - Run `deno task check` +4. **Documentation**: Update any relevant documentation with your changes. +5. **Commit and PR**: Commit your changes and submit a PR for review. +6. **CI Checks**: Ensure that all Continuous Integration (CI) checks pass + successfully. +7. **Review Process**: A maintainer will review your PR, usually within a few + days. + +#### Work-in-Progress (WIP) PRs + +If you’d like early feedback on your work, you can create a PR with the prefix +`[WIP]` to indicate that it is still under development and not ready for +merging. + +### Testing Your Contributions + +Since this repository contains integrations that must be tested against a deco +site, you cannot test your contributions in isolation. Please refer to this +[Helpful content](https://www.notion.so/decocx/Helpful-Community-Content-101eec7ce64f4becaebb685dd9571e24) +for instructions on how to set up a deco site for testing purposes. + +### Use of Third-Party Code + +- Ensure that any third-party code included in your contributions comes with the + appropriate licenses. + +### Releasing a New Version + +We follow semantic versioning, and all apps in this repository are versioned +collectively using git tags. To release a new version: + +1. Fork the repository and create a pull request with your changes. +2. After the PR is approved and merged, request a maintainer to react the + releaser comment with the required emoji 👍 for **Patch** 🎉 for **Minor** 🚀 + for **Major** + +When your PR got merged, a new tag will arrive with the desired semver +modification. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..2b04acdad --- /dev/null +++ b/LICENSE @@ -0,0 +1,204 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 The Dapr Authors. + + and others that have contributed code to the public domain. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/MAINTAINERS.txt b/MAINTAINERS.txt new file mode 100644 index 000000000..70e7f287b --- /dev/null +++ b/MAINTAINERS.txt @@ -0,0 +1,3 @@ +guitavano +matheusgr +viktormarinho diff --git a/README.md b/README.md new file mode 100644 index 000000000..ed19abb41 --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +
+ +Discord +  +Deco Twitter +  +![Build Status](https://github.com/deco-cx/apps/workflows/ci/badge.svg?event=push&branch=main) + +
+ +# Deco Standard **Apps** Library + +The Deco Framework logo: A capybara in its natural habitat + +Welcome to the `deco-cx/apps` repository! This repository is home to a collection of powerful apps that can be seamlessly integrated into your deco sites. Here, we provide you with a brief overview of the deco framework and introduce the concept of apps. We'll also delve into the repository structure, how to contribute, and more. Read more about apps in the [docs](https://www.deco.cx/docs/en/concepts/app), also if you want to see apps in action check our [storefront](https://github.com/deco-sites/storefront) repository. + +## About the deco Framework + +Deco, formerly known as `live`, is a modern and versatile framework that empowers developers to build dynamic and interactive websites with ease. Apps are a fundamental component of deco, offering a way to bundle sets of blocks together, all configured through an intuitive UI and sharing a common state defined within the app module. + +## Repository Structure + +The `deco-cx/apps` repository is structured as a monorepo, housing a collection of individual apps, each stored in a dedicated subfolder. These apps can be installed separately, providing you with the flexibility to choose and integrate only the functionalities that suit your project's needs. + +The `deco.ts` file serves as a hub where you can specify the apps of this repository. However it is important to notice that whether you choose to create apps within this repository or within your own organization's repository, there are no limitations on where apps should be developed. + +## Overview + +At the core of all websites within this repository is the `website` app, located in the `website` folder. This app lays the foundation for websites, offering a set of common features that are essential regardless of whether your site is an e-commerce platform or not. We've also structured the repository to accommodate specific platforms, such as e-commerce platforms like VTEX, Shopify, VNDA, and more. These platform-specific apps depend on the `website` app, leveraging its shared features while adding platform-specific functionality. + +## Contributing + +Check [CONTRIBUTING.md](https://github.com/deco-cx/apps/blob/main/CONTRIBUTING.md) + +### Apps + +| App Name | Description | Manifest | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | +| Algolia | Algolia search integration. Provides loaders and workflows for searching and indexing on Algolia | [manifest](/algolia/manifest.gen.ts) | +| ai-assistant | AI Assistant is a hub for artificial intelligence (AI) assistants, allowing you to register your own AI assistants and invoke them through automated actions. | [manifest](/ai-assistant/manifest.gen.ts) | +| analytics | Analytics is a powerful data analysis tool, providing valuable insights into the performance and behavior of users on your application or platform. | [manifest](/analytics/manifest.gen.ts) | +| brand-assistant | Brand Assistant is a brand assistance tool. | [manifest](/brand-assistant/manifest.gen.ts) | +| commerce | A simple configurable start for any e-commerce platform, lets you switch between any of those | [manifest](/commerce/manifest.gen.ts) | +| $live | An app for compatibility with $live blocks. | [manifest](/compat/$live/manifest.gen.ts) | +| deco-sites/std | An app for compatibility with deco-sites/std app, contains various blocks merged from e-commerce apps. | [manifest](/compat/std/manifest.gen.ts) | +| decohub | The best place to find an app for your business case, here is where apps published by any developer in the deco ecosystem will live. | [manifest](/decohub/manifest.gen.ts) | +| implementation | App for project implementation details. | [manifest](/implementation/manifest.gen.ts) | +| Konfidency | An app that uses extension block to add Aggregate Rating to products by integrating with the "[Konfidency](https://www.konfidency.com.br/)" provider. | [manifest](/konfidency/manifest.gen.ts) | +| Linx | The app for e-commerce that uses Linx as a platform. | [manifest](/linx/manifest.gen.ts) | +| nuvemshop | The app for e-commerce that uses Nuvemshop as a platform. | [manifest](/nuvemshop/manifest.gen.ts) | +| openai | Connects to OpenAI services to generate AI-powered content. | [manifest](/openai/manifest.gen.ts) | +| power-reviews | Power Reviews is an integration to show reviews and ratings of your products. It allow your customers to give a rating, write texts, emphasis pros/cons and upload images and videos. | [manifest](/power-reviews/manifest.gen.ts) | +| Resend | App for sending emails using [https://resend.com/](https://resend.com/) | [manifest](/resend/manifest.gen.ts) | +| EmailJS | App for sending emails using [https://www.emailjs.com/](https://www.emailjs.com/) | [manifest](/emailjs/manifest.gen.ts) | +| Shopify | The app for e-commerce that uses Shopify as a platform. | [manifest](/shopify/manifest.gen.ts) | +| sourei | Digitalize your business with Sourei. Offering a wide range of digital solutions, from domain registration to advanced project infrastructure. | [manifest](/sourei/manifest.gen.ts) | +| typesense | Typesense search integration. Provides loaders and workflows for searching and indexing on Typesense. | [manifest](/typesense/manifest.gen.ts) | +| Verified Reviews | An app that uses extension block to add Aggregate Rating to products by integrating with the "[Opiniões Verificadas](https://www.opinioes-verificadas.com.br/br/)" provider. | [manifest](/verified-reviews/manifest.gen.ts) | +| VNDA | The app for e-commerce that uses VNDA as a platform. | [manifest](/vnda/manifest.gen.ts) | +| VTEX | The app for e-commerce that uses VTEX as a platform. | [manifest](/vtex/manifest.gen.ts) | +| Wake | The app for e-commerce that uses Wake as a platform. | [manifest](/wake/manifest.gen.ts) | +| Weather | Weather is an application that provides accurate and up-to-date weather information. | [manifest](/weather/manifest.gen.ts) | +| Website | The base app of any website. Contains `Page.tsx` block and other common loaders like image and fonts. | [manifest](/website/manifest.gen.ts) | +| Workflows | App for managing workflows. | [manifest](/workflows/manifest.gen.ts) | + +## E-commerce Integrations - Status + +| Integrations | Home | PLP | PDP | Cart | Checkout proxy | Order placed proxy | My account proxy | +| :--------------------------------------------------------------------------------------------------------- | :--- | :-- | :-- | :--- | :------------- | :----------------- | :--------------- | +| [VTEX](https://github.com/deco-cx/apps/blob/main/vtex/README.md) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [VNDA](https://github.com/deco-cx/apps/blob/main/vnda/README.md) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Shopify](https://github.com/deco-cx/apps/blob/b072c1fdfab8d5f1647ed42f9dbaae618f28f05f/shopify/README.md) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | +| [Linx](https://github.com/deco-cx/apps/blob/main/linx/README.md) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Linx impulse | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| [Nuvemshop](https://github.com/deco-cx/apps/blob/main/nuvemshop/README.MD) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️ | +| [Wake](https://github.com/deco-cx/apps/blob/main/wake/README.md) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | + +## Review Integrations - Status + +| Integrations | Extension PDP | Extension ProductList | Extension Listing Page | Submit Review | +| :--------------------------------------------------------------------------------------- | :------------ | :-------------------- | :--------------------- | :------------ | +| [Power Reviews](https://github.com/deco-cx/apps/blob/main/power-reviews/README.md) | ✅ | ✅ | ✅ | ✅ | +| [Verified Reviews](https://github.com/deco-cx/apps/blob/main/verified-reviews/README.md) | ✅ | ✅ | ✅ | 🔴 | +| [Konfidency](https://github.com/deco-cx/apps/blob/main/konfidency/README.md) | ✅ | 🔴 | 🔴 | 🔴 | + +#### Adding a new app to Deco Hub + +In order to make your app available to be installable in any deco site, just import/export your app inside decohub/apps folder. + +## Thanks to all contributors + + + + diff --git a/admin/types.ts b/admin/types.ts new file mode 100644 index 000000000..ad617df3b --- /dev/null +++ b/admin/types.ts @@ -0,0 +1,41 @@ +import { type fjp } from "./deps.ts"; +import { type Resolvable } from "@deco/deco"; +export interface Pagination { + data: T[]; + page: number; + pageSize: number; + total: number; +} +export interface PatchState { + type: "patch-state"; + payload: fjp.Operation[]; + revision: string; +} +export interface FetchState { + type: "fetch-state"; +} +export interface StatePatched { + type: "state-patched"; + payload: fjp.Operation[]; + revision: string; + // Maybe add data and user info in here + metadata?: unknown; +} +export interface StateFetched { + type: "state-fetched"; + payload: State; +} +export interface OperationFailed { + type: "operation-failed"; + code: "UNAUTHORIZED" | "INTERNAL_SERVER_ERROR"; + reason: string; +} +export type Acked = T & { + ack: string; +}; +export interface State { + decofile: Record; + revision: string; +} +export type Commands = PatchState | FetchState; +export type Events = StatePatched | StateFetched | OperationFailed; diff --git a/admin/widgets.ts b/admin/widgets.ts new file mode 100644 index 000000000..6ed9942ee --- /dev/null +++ b/admin/widgets.ts @@ -0,0 +1,71 @@ +/** + * @format image-uri + */ +export type ImageWidget = string; + +/** + * @format html + */ +export type HTMLWidget = string; + +/** + * @format video-uri + */ +export type VideoWidget = string; + +/** + * @format dynamic-options + * @options website/loaders/options/routes.ts + */ +export type SiteRoute = string; + +/** + * @format map + */ +export type MapWidget = string; + +/** + * @format textarea + */ +export type TextArea = string; + +/** + * @format rich-text + */ +export type RichText = string; + +/** + * @format color-input + * @default #000000 + */ +export type Color = string; + +/** + * @format code + * @language css + */ +export type CSS = string; + +/** + * @format code + * @language typescript + */ +export type TypeScript = string; + +/** + * @format code + * @language json + */ +export type Json = string; + +/** + * @format file-uri + * @accept text/csv + */ +export type CSVWidget = string; + +/** + * @format file-uri + * @accept application/pdf + */ +export type PDFWidget = string; diff --git a/ai-assistants/actions/awsUploadImage.ts b/ai-assistants/actions/awsUploadImage.ts new file mode 100644 index 000000000..34eb20426 --- /dev/null +++ b/ai-assistants/actions/awsUploadImage.ts @@ -0,0 +1,66 @@ +import base64ToBlob from "../utils/blobConversion.ts"; +import { AssistantIds } from "../types.ts"; +import { AppContext } from "../mod.ts"; +import { logger, meter, ValueType } from "@deco/deco/o11y"; +const stats = { + awsUploadImageError: meter.createCounter("assistant_aws_upload_error", { + unit: "1", + valueType: ValueType.INT, + }), +}; +export interface AWSUploadImageProps { + file: string | ArrayBuffer | null; + assistantIds?: AssistantIds; +} +// TODO(ItamarRocha): Check if possible to upload straight to bucket instead of using presigned url +async function getSignedUrl( + mimetype: string, + ctx: AppContext, +): Promise { + const randomID = crypto.randomUUID(); + const name = `${randomID}.${mimetype.split("/")[1]}`; + // Get signed URL from S3 + const s3Params = { + Bucket: ctx.assistantAwsProps?.assistantBucketName.get?.() ?? "", + Key: name, + ContentType: mimetype, + ACL: "public-read", + }; + const uploadURL = await ctx.s3?.getSignedUrlPromise("putObject", s3Params); + return uploadURL as string; +} +async function uploadFileToS3(presignedUrl: string, data: Blob) { + const response = await fetch(presignedUrl, { method: "PUT", body: data }); + return response; +} +// TODO(ItamarRocha): Rate limit +export default async function awsUploadImage( + awsUploadImageProps: AWSUploadImageProps, + _req: Request, + ctx: AppContext, +) { + const assistantId = awsUploadImageProps.assistantIds?.assistantId; + const threadId = awsUploadImageProps.assistantIds?.threadId; + const blobData = base64ToBlob( + awsUploadImageProps.file, + "image", + awsUploadImageProps.assistantIds, + ); + const uploadURL = await getSignedUrl(blobData.type, ctx); + const uploadResponse = await uploadFileToS3(uploadURL, blobData); + if (!uploadResponse.ok) { + stats.awsUploadImageError.add(1, { + assistantId, + }); + throw new Error(`Failed to upload file: ${uploadResponse.statusText}`); + } + logger.info({ + assistantId: assistantId, + threadId: threadId, + context: "awsUploadImage", + response: JSON.stringify(uploadResponse), + uploadUrl: uploadURL, + }); + const imageUrl = new URL(uploadURL); + return `${imageUrl.origin}${imageUrl.pathname}`; +} diff --git a/ai-assistants/actions/chat.ts b/ai-assistants/actions/chat.ts new file mode 100644 index 000000000..10f777d37 --- /dev/null +++ b/ai-assistants/actions/chat.ts @@ -0,0 +1,178 @@ +import { AppContext } from "../mod.ts"; +import { messageProcessorFor } from "../chat/messages.ts"; +import { Notify, Queue } from "../deps.ts"; +import { badRequest, notFound } from "@deco/deco"; +export interface Props { + thread?: string; + assistant: string; + message?: string; +} +/** + * Processes messages from the message queue. + * @param {Queue} q - The message queue. + * @param {Notify} abort - The notification object for aborting the message processing. + * @param {(c: ChatMessage) => Promise} processor - The function for processing each message. + * @returns {Promise} - A promise representing the completion of the message processing. + */ +const process = async ( + q: Queue, + abort: Notify, + processor: (c: ChatMessage) => Promise, +) => { + while (true) { + const message = await Promise.race([ + abort.notified(), + q.pop(), + ]); + if (typeof message !== "object") { + break; + } + await Promise.race([ + processor(message), + ]); + } +}; +export interface MessageContentText { + type: "text"; + value: string; + options?: string[]; +} +export interface MessageContentFile { + type: "file"; + fileId: string; +} +export interface ReplyMessage { + threadId: string; + messageId: string; + type: "message" | "error"; + content: Array; + role: "user" | "assistant"; +} +export interface FunctionCall { + name: string; + props: unknown; +} +export interface FunctionCallReply extends FunctionCall { + response: T; +} +export interface ReplyStartFunctionCall { + threadId: string; + messageId: string; + type: "start_function_call"; + content: FunctionCall; +} +export interface ReplyFunctionCalls { + threadId: string; + messageId: string; + type: "function_calls"; + content: FunctionCallReply[]; +} +export type Reply = + | ReplyMessage + | ReplyFunctionCalls + | ReplyStartFunctionCall; +export interface ChatMessage { + text: string; + reply: (reply: Reply) => void; +} +/** + * Initializes a WebSocket chat connection and processes incoming messages. + * @param {Props} props - The properties for the chat session. + * @param {Request} req - The HTTP request object containing the WebSocket connection. + * @param {AppContext} ctx - The application context. + * @returns {Response} - The HTTP response object. + */ +export default async function openChat( + props: Props, + req: Request, + ctx: AppContext, +): Promise< + Response | { + replies: Reply[]; + thread: string; + } +> { + if (!props.assistant) { + notFound(); + } + const assistant = ctx.assistants[props.assistant]; + if (!assistant) { + notFound(); + } + const threads = ctx.openAI.beta.threads; + const threadId = props.thread; + const threadPromise = threadId + ? threads.retrieve(threadId) + : threads.create(); + const processorPromise = assistant.then(async (aiAssistant) => + messageProcessorFor(aiAssistant, ctx, await threadPromise) + ); + if (req.headers.get("upgrade") !== "websocket") { + if (!props.message) { + badRequest({ message: "message is required when websocket is not used" }); + } + const processor = await processorPromise; + const replies: Reply[] = []; + await processor({ + text: props.message!, + reply: (reply) => replies.push(reply), + }); + return { replies, thread: (await threadPromise).id }; + } + const { socket, response } = Deno.upgradeWebSocket(req); + const abort = new Notify(); + const messagesQ = new Queue(); + if (props.message) { + messagesQ.push({ + text: props.message, + reply: (replyMsg) => socket.send(JSON.stringify(replyMsg)), + }); + } + /** + * Handles the WebSocket connection on open event. + */ + socket.onopen = async () => { + process( + messagesQ, + abort, + await processorPromise.catch((err) => { + socket.send( + `An error was suddently ocurred when message processor was created. ${err}`, + ); + socket.close(); + abort.notifyAll(); + throw err; + }), + ); + assistant.then((aiAssistant) => { + socket.send(JSON.stringify({ + isWelcomeMessage: true, + threadId: aiAssistant.threadId, + assistantId: aiAssistant.id, + type: "message", + content: [{ + type: "text", + value: aiAssistant.welcomeMessage ?? "Welcome to the chat!", + }], + role: "assistant", + })); + }); + }; + /** + * Handles the WebSocket connection on close event. + */ + socket.onclose = () => { + abort.notifyAll(); + }; + /** + * Handles the WebSocket connection on message event. + * @param {MessageEvent} event - The WebSocket message event. + */ + socket.onmessage = (event) => { + messagesQ.push({ + text: event.data, + reply: (replyMsg) => socket.send(JSON.stringify(replyMsg)), + }); + }; + return response; +} diff --git a/ai-assistants/actions/describeImage.ts b/ai-assistants/actions/describeImage.ts new file mode 100644 index 000000000..22296dc35 --- /dev/null +++ b/ai-assistants/actions/describeImage.ts @@ -0,0 +1,91 @@ +import { AssistantIds } from "../types.ts"; +import { AppContext } from "../mod.ts"; +import { logger, meter, ValueType } from "@deco/deco/o11y"; +import { shortcircuit } from "@deco/deco"; +const stats = { + promptTokens: meter.createHistogram("assistant_image_prompt_tokens", { + description: "Tokens used in Sales Assistant Describe Image Input - OpenAI", + valueType: ValueType.INT, + }), + completionTokens: meter.createHistogram("assistant_image_completion_tokens", { + description: + "Tokens used in Sales Assistant Describe Image Output - OpenAI", + valueType: ValueType.INT, + }), + describeImageError: meter.createCounter("assistant_describe_image_error", { + unit: "1", + valueType: ValueType.INT, + }), +}; +export interface DescribeImageProps { + uploadURL: string; + userPrompt: string; + assistantIds?: AssistantIds; +} +// TODO(ItamarRocha): Rate limit +// TODO(@ItamarRocha): Refactor to use https://github.com/deco-cx/apps/blob/main/openai/loaders/vision.ts +export default async function describeImage( + describeImageProps: DescribeImageProps, + _req: Request, + ctx: AppContext, +) { + const assistantId = describeImageProps.assistantIds?.assistantId; + const threadId = describeImageProps.assistantIds?.threadId; + try { + const response = await ctx.openAI.chat.completions.create({ + model: "gpt-4-vision-preview", + messages: [ + { + role: "user", + content: [ + { + type: "text", + text: + `Describe this image in few words focus on it's main characteristics. + This description will be used to search similar items in an e-commerce store, + so describe name of the product and other relevant information. Use NO MORE than 3 words to describe the product. + Avoid using colors. Also, take into consideration the user prompt and describe the object it + focuses on if there is one. Output should be 1-2 sentences and should be a request summarizing + the user's need/request to a sales assistant that will search the product in an e-commerce store. + + * Use the same language as the user prompt in your answer * + + User prompt: + ${describeImageProps.userPrompt}`, + }, + { + type: "image_url", + image_url: { + "url": describeImageProps.uploadURL, + }, + }, + ], + }, + ], + }); + logger.info({ + assistantId: assistantId, + threadId: threadId, + context: "describeImage", + response: JSON.stringify(response), + props: describeImageProps, + }); + stats.promptTokens.record(response.usage?.prompt_tokens ?? 0, { + assistant_id: assistantId, + }); + stats.completionTokens.record(response.usage?.completion_tokens ?? 0, { + assistant_id: assistantId, + }); + return response; + } catch (error) { + stats.describeImageError.add(1, { + assistantId, + }); + shortcircuit( + new Response(JSON.stringify({ error: error.error.message }), { + status: error.status, + headers: error.headers, + }), + ); + } +} diff --git a/ai-assistants/actions/transcribeAudio.ts b/ai-assistants/actions/transcribeAudio.ts new file mode 100644 index 000000000..453417b22 --- /dev/null +++ b/ai-assistants/actions/transcribeAudio.ts @@ -0,0 +1,60 @@ +import base64ToBlob from "../utils/blobConversion.ts"; +import { AssistantIds } from "../types.ts"; +import { AppContext } from "../mod.ts"; +import { logger, meter, ValueType } from "@deco/deco/o11y"; +const stats = { + audioSize: meter.createHistogram("assistant_transcribe_audio_size", { + description: + "Audio size used in Sales Assistant Transcribe Image Input - OpenAI", + unit: "s", + valueType: ValueType.DOUBLE, + }), + transcribeAudioError: meter.createCounter( + "assistant_transcribe_audio_error", + { + unit: "1", + valueType: ValueType.INT, + }, + ), +}; +export interface TranscribeAudioProps { + file: string | ArrayBuffer | null; + assistantIds?: AssistantIds; + audioDuration: number; +} +// TODO(ItamarRocha): Rate limit +export default async function transcribeAudio( + transcribeAudioProps: TranscribeAudioProps, + _req: Request, + ctx: AppContext, +) { + const assistantId = transcribeAudioProps.assistantIds?.assistantId; + const threadId = transcribeAudioProps.assistantIds?.threadId; + if (!transcribeAudioProps.file) { + stats.transcribeAudioError.add(1, { + assistantId, + }); + throw new Error("Audio file is empty"); + } + const blobData = base64ToBlob( + transcribeAudioProps.file, + "audio", + transcribeAudioProps.assistantIds, + ); + const file = new File([blobData], "input.wav", { type: "audio/wav" }); + stats.audioSize.record(transcribeAudioProps.audioDuration, { + assistant_id: assistantId, + }); + const response = await ctx.openAI.audio.transcriptions.create({ + model: "whisper-1", + file: file, + }); + logger.info({ + assistantId: assistantId, + threadId: threadId, + context: "transcribeAudio", + subcontext: "response", + response: JSON.stringify(response), + }); + return response; +} diff --git a/ai-assistants/chat/messages.ts b/ai-assistants/chat/messages.ts new file mode 100644 index 000000000..169eab460 --- /dev/null +++ b/ai-assistants/chat/messages.ts @@ -0,0 +1,319 @@ +import { Context, type JSONSchema7, lazySchemaFor } from "@deco/deco"; +import { meter, ValueType } from "@deco/deco/o11y"; +import { weakcache } from "../../utils/weakcache.ts"; +import { + ChatMessage, + FunctionCallReply, + Reply, + ReplyMessage, +} from "../actions/chat.ts"; +import { + AssistantCreateParams, + RequiredActionFunctionToolCall, + Thread, +} from "../deps.ts"; +import { threadMessageToReply, Tokens } from "../loaders/messages.ts"; +import { AIAssistant, AppContext } from "../mod.ts"; +import { dereferenceJsonSchema } from "../schema.ts"; +const stats = { + latency: meter.createHistogram("assistant_latency", { + description: + "assistant latency (time it takes from the moment the server receives the request to the moment it sends the response)", + unit: "ms", + valueType: ValueType.DOUBLE, + }), +}; +// Max length of instructions. The maximum context of the assistant is 32K chars. We use 25K for instructions to be safe. +const MAX_INSTRUCTIONS_LENGTH = 25000; +const notUndefined = (v: T | undefined): v is T => v !== undefined; +const toolsCache = new weakcache.WeakLRUCache({ + cacheSize: 16, // up to 16 different schemas stored here. +}); +/** + * Builds assistant tools that can be used by OpenAI assistant to execute actions based on users requests. + * @param assistant the assistant that will handle the request + * @returns an array of available functions that can be used. + */ +const appTools = async ( + assistant: AIAssistant, +): Promise => { + const ctx = Context.active(); + const assistantsKey = assistant.availableFunctions?.join(",") ?? "all"; + const revision = await ctx.release!.revision(); + const cacheKey = `${assistantsKey}@${revision}`; + if (toolsCache.has(cacheKey)) { + return toolsCache.get(cacheKey)!; + } + const toolsPromise = ctx.runtime!.then(async (runtime) => { + const schemas = await lazySchemaFor(ctx).value; + const functionKeys = assistant.availableFunctions ?? Object.keys({ + ...runtime.manifest.loaders, + ...runtime.manifest.actions, + }); + const tools = functionKeys.map((functionKey) => { + const functionDefinition = btoa(functionKey); + const schema = schemas.definitions[functionDefinition]; + if ( + (schema as { + ignoreAI?: boolean; + })?.ignoreAI + ) { + return undefined; + } + const propsRef = (schema?.allOf?.[0] as JSONSchema7)?.$ref; + if (!propsRef) { + return undefined; + } + const dereferenced = dereferenceJsonSchema({ + $ref: propsRef, + ...schemas, + }); + if ( + dereferenced.type !== "object" || + (dereferenced.oneOf || dereferenced.anyOf || + dereferenced?.allOf || dereferenced?.enum || dereferenced?.not) + ) { + return undefined; + } + return { + type: "function" as const, + function: { + name: functionKey, + description: + `Usage for: ${schema?.description}. Example: ${schema?.examples}`, + parameters: { + ...dereferenced, + definitions: undefined, + root: undefined, + }, + }, + }; + }).filter(notUndefined); + toolsCache.set(ctx, tools); + return tools; + }); + toolsCache.set(cacheKey, toolsPromise); + return toolsPromise; +}; +export interface ProcessorOpts { + assistantId: string; + instructions: string; +} +const sleep = (ns: number) => new Promise((resolve) => setTimeout(resolve, ns)); +const cache: Record = {}; +const invokeFor = ( + ctx: AppContext, + assistant: AIAssistant, + onFunctionCallStart: ( + tool: RequiredActionFunctionToolCall, + props: unknown, + ) => void, + onFunctionCallEnd: ( + tool: RequiredActionFunctionToolCall, + props: unknown, + response: unknown, + ) => void, +) => { + return async (call: RequiredActionFunctionToolCall) => { + try { + const props = JSON.parse(call.function.arguments || "{}"); + const cacheKey = `${call.function.arguments}${call.function.name}`; + const assistantProps = assistant?.useProps?.(props) ?? props; + cache[cacheKey] ??= ctx.invoke( + // deno-lint-ignore no-explicit-any + call.function.name as any, + assistantProps, + ); + onFunctionCallStart(call, assistantProps); + const invokeResponse = await cache[cacheKey]; + onFunctionCallEnd(call, assistantProps, invokeResponse); + return { + tool_call_id: call.id, + output: JSON.stringify(invokeResponse), + }; + } catch (err) { + console.error("invoke error", err); + return { + tool_call_id: call.id, + output: "[]", + }; + } + }; +}; +/** + * Creates a message processor function for the given AI assistant and context. + * @param {AIAssistant} assistant - The AI assistant for processing messages. + * @param {AppContext} ctx - The application context. + * @returns {Promise<(message: ChatMessage) => void>} - A function that processes incoming chat messages. + */ +export const messageProcessorFor = async ( + assistant: AIAssistant, + ctx: AppContext, + thread: Thread, +) => { + const openAI = ctx.openAI; + const threads = openAI.beta.threads; + const instructions = + `${ctx.instructions}. Introduce yourself as ${assistant.name}. ${assistant.instructions}. The files uploaded to the assistant should + give you a good context of how the products you are dealing with are formatted. ${ + assistant.prompts + ? "Below are arbitrary prompt that gives you information about the current context, it can be empty." + : "" + }\n${ + (assistant.prompts ?? []).map((prompt) => + `this is the ${prompt.context}: ${prompt.content}` + ) + }. DO NOT CHANGE FUNCTIONS NAMES, do not remove .ts at the end of function name. do not remove / at the end of function name. + If you are positive that your response contains the information that the user requests (like product descriptions, product names, prices, colors, and sizes), add an ${Tokens.POSITIVE} symbol at the end of the phrase. Otherwise, add a ${Tokens.NEGATIVE} symbol. + For example, if the user asks about product availability and you have the information, respond with "The product is in stock. @". If you don't have the information, respond with "I'm sorry, the product is currently unavailable. ${Tokens.NEGATIVE}". + `; + const assistantId = await ctx.assistant.then((assistant) => assistant.id); + const tools = await appTools(assistant); + // Update the assistant object with the thread and assistant id + assistant.threadId = thread.id; + assistant.id = assistantId; + /** + * Processes an incoming chat message. + * @param {ChatMessage} message - The incoming chat message. + * @returns {Promise} - A promise representing the completion of message processing. + */ + return async ({ text: content, reply: _reply }: ChatMessage) => { + // send message + const start = performance.now(); + await threads.messages.create(thread.id, { + content, + role: "user", + }); + // create run + const run = await threads.runs.create(thread.id, { + model: typeof assistant.model === "object" + ? assistant.model.custom + : assistant.model, + assistant_id: assistantId, + instructions: instructions.slice(0, MAX_INSTRUCTIONS_LENGTH), + tools, + }); + const messageId = run.id; + // Wait for the assistant answer + const functionCallReplies: FunctionCallReply[] = []; + // Reply to the user + const reply = (message: Reply) => { + assistant.onMessageSent?.({ + assistantId: run.assistant_id, + threadId: thread.id, + runId: run.id, + model: run.model, + message, + }); + return _reply(message); + }; + assistant.onMessageReceived?.({ + assistantId: run.assistant_id, + threadId: thread.id, + runId: run.id, + model: run.model, + message: { type: "text", value: content }, + }); + const invoke = invokeFor(ctx, assistant, (call, props) => { + reply({ + threadId: thread.id, + messageId, + type: "start_function_call", + content: { + name: call.function.name, + props, + }, + }); + stats.latency.record(performance.now() - start, { + type: "start_function_call", + assistant_id: run.assistant_id, + }); + }, (call, props, response) => { + functionCallReplies.push({ + name: call.function.name, + props, + response, + }); + }); + let runStatus; + do { + runStatus = await threads.runs.retrieve(thread.id, run.id); + if (runStatus.status === "requires_action") { + const actions = runStatus.required_action!; + const outputs = actions.submit_tool_outputs; + const tool_outputs = await Promise.all(outputs.tool_calls.map(invoke)); + if (tool_outputs.length === 0) { + const message: ReplyMessage = { + messageId: Date.now().toString(), + threadId: thread.id, + type: "error", + content: [], + role: "assistant", + }; + reply(message); + return; + } + await threads.runs.submitToolOutputs(thread.id, run.id, { + tool_outputs, + }); + runStatus = await threads.runs.retrieve(thread.id, run.id); + } + await sleep(500); + } while (["in_progress", "queued"].includes(runStatus.status)); + const messages = await threads.messages.list(thread.id); + const threadMessages = messages.data; + const lastMsg = threadMessages + .findLast((message) => + message.run_id == run.id && message.role === "assistant" + ); + if (!lastMsg) { + // TODO(@mcandeia) in some cases the bot didn't respond anything. + const message: ReplyMessage = { + messageId: Date.now().toString(), + threadId: thread.id, + type: "error", + content: [], + role: "assistant", + }; + reply(message); + return; + } + const replyMessage = threadMessageToReply(lastMsg); + // multi tool use parallel seems to be some sort of openai bug, and it seems to have no solution yet. + // https://community.openai.com/t/model-tries-to-call-unknown-function-multi-tool-use-parallel/490653 + // It's an error that only happens every now and then. Open ai tries to call "multi_tool_use.parallel" function that doesn't even exist and isn't even in the OpenAI documentation + // If functionCallReplies is not an array it should also be considered an error + if ( + (functionCallReplies.length === 1 && + functionCallReplies[0].name === "multi_tool_use.parallel") || + !Array.isArray(functionCallReplies) + ) { + const message: ReplyMessage = { + messageId: Date.now().toString(), + threadId: thread.id, + type: "error", + content: [], + role: "assistant", + }; + reply(message); + } else { + reply(replyMessage); + stats.latency.record(performance.now() - start, { + type: "text", + assistant_id: run.assistant_id, + }); + } + if (functionCallReplies.length > 0) { + reply({ + threadId: thread.id, + messageId, + type: "function_calls" as const, + content: functionCallReplies, + }); + stats.latency.record(performance.now() - start, { + type: "function_calls", + assistant_id: run.assistant_id, + }); + } + }; +}; diff --git a/ai-assistants/deps.ts b/ai-assistants/deps.ts new file mode 100644 index 000000000..8f283a6dc --- /dev/null +++ b/ai-assistants/deps.ts @@ -0,0 +1,18 @@ +export type { + Assistant, + AssistantCreateParams, +} from "https://deno.land/x/openai@v4.19.1/resources/beta/assistants/assistants.ts"; + +export { Notify } from "https://deno.land/x/async@v2.0.2/notify.ts"; +export { Queue } from "https://deno.land/x/async@v2.0.2/queue.ts"; +export type { + MessageContentImageFile, + MessageContentText, + ThreadMessage, +} from "https://deno.land/x/openai@v4.19.1/resources/beta/threads/messages/messages.ts"; +export type { + RequiredActionFunctionToolCall, +} from "https://deno.land/x/openai@v4.19.1/resources/beta/threads/runs/runs.ts"; +export type { + Thread, +} from "https://deno.land/x/openai@v4.19.1/resources/beta/threads/threads.ts"; diff --git a/ai-assistants/hooks/useFileUpload.ts b/ai-assistants/hooks/useFileUpload.ts new file mode 100644 index 000000000..7ce05dd7a --- /dev/null +++ b/ai-assistants/hooks/useFileUpload.ts @@ -0,0 +1,9 @@ +import { invoke } from "../runtime.ts"; + +const state = { + describeImage: invoke["ai-assistants"].actions.describeImage, + awsUploadImage: invoke["ai-assistants"].actions.awsUploadImage, + transcribeAudio: invoke["ai-assistants"].actions.transcribeAudio, +}; + +export const useFileUpload = () => state; diff --git a/ai-assistants/loaders/messages.ts b/ai-assistants/loaders/messages.ts new file mode 100644 index 000000000..51c26dca5 --- /dev/null +++ b/ai-assistants/loaders/messages.ts @@ -0,0 +1,90 @@ +import { ReplyMessage } from "../actions/chat.ts"; +import { + MessageContentImageFile, + MessageContentText, + ThreadMessage, +} from "../deps.ts"; +import { AppContext } from "../mod.ts"; +export interface Props { + thread: string; + after?: string; + before?: string; +} + +export const Tokens = { + POSITIVE: "@", + NEGATIVE: "#", + OPTIONS: "&&&", +}; + +const normalize = (strContent: string) => { + const hasOptions = strContent.includes(Tokens.OPTIONS); + + if (!hasOptions) { + return strContent.endsWith(Tokens.POSITIVE) || + strContent.endsWith(Tokens.NEGATIVE) + ? strContent.slice(0, strContent.length - 2) + : strContent; + } + + return strContent.split(Tokens.OPTIONS)[0]; +}; + +export const getToken = (message: ThreadMessage): string => { + const text = (message.content[0] as MessageContentText).text?.value; + if (!text) { + return Tokens.NEGATIVE; + } + return text.endsWith(Tokens.POSITIVE) ? Tokens.POSITIVE : Tokens.NEGATIVE; +}; + +export const threadMessageToReply = (message: ThreadMessage): ReplyMessage => { + return { + threadId: message.thread_id!, + messageId: message.run_id!, + type: "message", + content: message.content.map((cnt) => + isFileContent(cnt) ? { type: "file", fileId: cnt.image_file.file_id! } : { + type: "text", + value: normalize(cnt.text!.value), + options: getOptions(cnt), + } + ), + role: message.role, + }; +}; + +// Function to for the token OPTIONS in the message content and get everything after it to get the options. +// If your response contains options for the user to choose from, make sure to include the ${Tokens.OPTIONS} symbol in your response, followed by the options separated by commas, followed by another ${Tokens.OPTIONS} symbol. +const getOptions = (content: MessageContentText): string[] => { + const text = content.text?.value; + + if (!text) { + return []; + } + const options = text.split(Tokens.OPTIONS)[1]; + + if (!options) { + return []; + } + + return options.split(",").map((option) => option.trim()); +}; + +const isFileContent = ( + v: MessageContentImageFile | MessageContentText, +): v is MessageContentImageFile => { + return (v as MessageContentImageFile)?.image_file?.file_id !== undefined; +}; + +export default async function messages( + { thread, after, before }: Props, + _req: Request, + ctx: AppContext, +): Promise { + const messages = await ctx.openAI.beta.threads.messages.list(thread, { + after, + before, + }); + return messages.data.map(threadMessageToReply); +} diff --git a/ai-assistants/logo.png b/ai-assistants/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..be41da6b9af6239e553488a7670fbcaee2812fa6 GIT binary patch literal 2692 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 literal 0 HcmV?d00001 diff --git a/ai-assistants/manifest.gen.ts b/ai-assistants/manifest.gen.ts new file mode 100644 index 000000000..84b6b47d7 --- /dev/null +++ b/ai-assistants/manifest.gen.ts @@ -0,0 +1,27 @@ +// DO NOT EDIT. This file is generated by deco. +// This file SHOULD be checked into source version control. +// This file is automatically updated during development when running `dev.ts`. + +import * as $$$$$$$$$0 from "./actions/awsUploadImage.ts"; +import * as $$$$$$$$$1 from "./actions/chat.ts"; +import * as $$$$$$$$$2 from "./actions/describeImage.ts"; +import * as $$$$$$$$$3 from "./actions/transcribeAudio.ts"; +import * as $$$0 from "./loaders/messages.ts"; + +const manifest = { + "loaders": { + "ai-assistants/loaders/messages.ts": $$$0, + }, + "actions": { + "ai-assistants/actions/awsUploadImage.ts": $$$$$$$$$0, + "ai-assistants/actions/chat.ts": $$$$$$$$$1, + "ai-assistants/actions/describeImage.ts": $$$$$$$$$2, + "ai-assistants/actions/transcribeAudio.ts": $$$$$$$$$3, + }, + "name": "ai-assistants", + "baseUrl": import.meta.url, +}; + +export type Manifest = typeof manifest; + +export default manifest; diff --git a/ai-assistants/mod.ts b/ai-assistants/mod.ts new file mode 100644 index 000000000..ae3b64a21 --- /dev/null +++ b/ai-assistants/mod.ts @@ -0,0 +1,207 @@ +import AWS from "https://esm.sh/aws-sdk@2.1585.0"; +import { deferred } from "std/async/deferred.ts"; +import openai, { + Props as OpenAIProps, + State as OpenAIState, +} from "../openai/mod.ts"; +import { Assistant } from "./deps.ts"; +import manifest, { Manifest } from "./manifest.gen.ts"; +import { Secret } from "../website/loaders/secret.ts"; +import { PreviewContainer } from "../utils/preview.tsx"; +import { + type App, + type AppContext as AC, + type AppManifest, + asResolved, + type AvailableActions, + type AvailableLoaders, + isDeferred, +} from "@deco/deco"; +import { isAwaitable } from "@deco/deco/utils"; +export type GPTModel = + | "gpt-4-0613" + | "gpt-4-0314" + | "gpt-4-1106-preview" + | "gpt-4" + | "gpt-3.5-turbo-1106" + | "gpt-3.5-turbo-16k" + | "gpt-3.5-turbo-16k-0613" + | "gpt-3.5-turbo" + | "gpt-3.5-turbo-0613"; +/** + * Represents an AI Assistant with specific capabilities and configurations. + * @template TManifest - The type of the AppManifest associated with the AI Assistant. + */ +export interface AIAssistant { + /** + * The name of the AI Assistant. + */ + name: string; + /** + * Optional instructions or guidelines for the AI Assistant. + */ + instructions?: string; + /** + * Optional array of prompts to provide context for the AI Assistant. + */ + prompts?: Prompt[]; + /** + * Optional welcome message to be displayed when the chat session starts. + */ + welcomeMessage?: string; + /** + * Optional list of available functions (actions or loaders) that the AI Assistant can perform. + */ + availableFunctions?: Array< + AvailableActions | AvailableLoaders + >; + /** + * Optional function to customize the handling of properties (props) passed to the AI Assistant. + * It takes a set of properties and returns a modified set of properties. + * @param {unknown} props - The properties passed to the AI Assistant. + * @returns {unknown} - The modified properties. + */ + useProps?: (props: unknown) => unknown; + /** + * Optional function to log the received messages from the user. + * @param {Log} logInfo - User message / information. + * @returns {void} - The modified properties. + */ + onMessageReceived?: (logInfo: Log) => void; + /** + * Optional function to log the received messages sent by the assistant. + * @param {Log} logInfo - Assistant message / information. + * @returns {void} - The modified properties. + */ + onMessageSent?: (logInfo: Log) => void; + /** + * The GPT model that will be used, if not specified the assistant model will be used. + */ + model?: GPTModel | { + custom: string; + }; + /** + * The Id of the assistant + */ + id?: string; + /** + * The Id of the assistant thread + */ + threadId?: string; +} +export interface Log { + assistantId: string; + threadId: string; + runId: string; + model: string; + message: object; +} +export interface Prompt { + content: string; + context: string; +} +export interface AssistantAwsProps { + assistantBucketRegion: Secret; + accessKeyId: Secret; + secretAccessKey: Secret; + assistantBucketName: Secret; +} +export interface Props extends OpenAIProps { + /** + * @description the assistant Id + */ + assistantId: string; + /** + * @description Instructions + */ + instructions?: string; + assistants?: AIAssistant[]; + assistantAwsProps?: AssistantAwsProps; + s3?: AWS.S3; +} +export interface State extends OpenAIState { + instructions?: string; + assistant: Promise; + assistants: Record>; + assistantAwsProps?: AssistantAwsProps; + s3?: AWS.S3; +} +/** + * @title Deco AI Assistant + * @description Create AI assistants on deco.cx. + * @category Tool + * @logo https://raw.githubusercontent.com/deco-cx/apps/main/ai-assistants/logo.png + */ +export default function App(state: Props): App, +]> { + const openAIApp = openai(state); + const assistantsAPI = openAIApp.state.openAI.beta.assistants; + // Sets assistantId only if state.assistants exists + const assistantId = (state.assistants?.[0] ?? null) !== null + ? state.assistantId + : ""; + return { + manifest, + state: { + ...openAIApp.state, + assistants: (state.assistants ?? []).filter(Boolean).reduce( + (acc, assistant) => { + const assistantDeferred = deferred(); + if (isDeferred(assistant)) { + const aiAssistant = assistant(); + if (isAwaitable(aiAssistant)) { + aiAssistant.then(assistantDeferred.resolve).catch( + assistantDeferred.reject, + ); + } else { + assistantDeferred.resolve(aiAssistant); + } + } else if (assistant) { + assistantDeferred.resolve(assistant); + } + return { [assistant.name]: assistantDeferred, ...acc }; + }, + {}, + ), + instructions: `${state.instructions ?? ""}`, + assistant: assistantsAPI.retrieve(assistantId), + s3: new AWS.S3({ + region: state.assistantAwsProps?.assistantBucketRegion.get?.() ?? + Deno.env.get("ASSISTANT_BUCKET_REGION"), + accessKeyId: state.assistantAwsProps?.accessKeyId.get?.() ?? + Deno.env.get("AWS_ACCESS_KEY_ID"), + secretAccessKey: state.assistantAwsProps?.secretAccessKey.get?.() ?? + Deno.env.get("AWS_SECRET_ACCESS_KEY"), + }), + assistantAwsProps: state.assistantAwsProps, + }, + dependencies: [openAIApp], + }; +} +export const onBeforeResolveProps = (props: Props) => { + if (Array.isArray(props?.assistants)) { + return { + ...props, + assistants: props.assistants.map((assistant) => + asResolved(assistant, true) + ), + }; + } + return props; +}; +export type AppContext = AC>; +export const preview = () => { + return { + Component: PreviewContainer, + props: { + name: "Deco AI Assistant", + owner: "deco.cx", + description: "Create AI assistants on deco.cx.", + logo: + "https://raw.githubusercontent.com/deco-cx/apps/main/ai-assistants/logo.png", + images: [], + tabs: [], + }, + }; +}; diff --git a/ai-assistants/runtime.ts b/ai-assistants/runtime.ts new file mode 100644 index 000000000..da42a2435 --- /dev/null +++ b/ai-assistants/runtime.ts @@ -0,0 +1,3 @@ +import { Manifest } from "./manifest.gen.ts"; +import { proxy } from "@deco/deco/web"; +export const invoke = proxy(); diff --git a/ai-assistants/schema.ts b/ai-assistants/schema.ts new file mode 100644 index 000000000..493aff815 --- /dev/null +++ b/ai-assistants/schema.ts @@ -0,0 +1,32 @@ +import { type JSONSchema7 } from "@deco/deco"; +const isJSONSchema = (v: unknown | JSONSchema7): v is JSONSchema7 & { + $ref: string; +} => { + return (typeof v === "object" && ((v as JSONSchema7)?.$ref !== undefined)); +}; +export function dereferenceJsonSchema( + schema: JSONSchema7 & { + definitions?: Record; + }, +) { + const resolveReference = ( + obj: unknown, + visited: Record, + ): JSONSchema7 => { + if (isJSONSchema(obj)) { + if (visited[obj["$ref"]]) { + return {}; + } + visited[obj["$ref"]] = true; + const [_, __, ref] = obj["$ref"].split("/"); + return resolveReference(schema?.definitions?.[ref], visited); + } else if (obj && typeof obj === "object") { + const recordObj = obj as Record; + for (const key in recordObj) { + recordObj[key] = resolveReference(recordObj[key], visited); + } + } + return obj as JSONSchema7; + }; + return resolveReference(schema, {}); +} diff --git a/ai-assistants/types.ts b/ai-assistants/types.ts new file mode 100644 index 000000000..eb8ff617e --- /dev/null +++ b/ai-assistants/types.ts @@ -0,0 +1,38 @@ +/** + * A personalized assistant configuration + */ +export interface AssistantPersonalization { + /** + * @title The assistant's name + */ + nickname?: string; + + /** + * @title The assistant's personality + */ + mood?: + | "Friendly" + | "Straight to the Point" + | "Humorous" + | "Professional" + | "Enthusiastic" + | "Informative" + | "Sarcastic" + | "Formal" + | "Energetic" + | "Curious" + | "Confident" + | "Helpful"; +} + +export interface AssistantIds { + /** + * @title The assistant's id + */ + assistantId?: string; + + /** + * @title The current thread id + */ + threadId?: string; +} diff --git a/ai-assistants/utils/blobConversion.ts b/ai-assistants/utils/blobConversion.ts new file mode 100644 index 000000000..826cba88c --- /dev/null +++ b/ai-assistants/utils/blobConversion.ts @@ -0,0 +1,51 @@ +import { AssistantIds } from "../types.ts"; +import { logger } from "@deco/deco/o11y"; +export default function base64ToBlob( + base64: string | ArrayBuffer | null, + context: string, + assistantIds?: AssistantIds, +): Blob { + let regex = + /^data:(audio\/[a-z0-9\-\+\.]+|video\/[a-z0-9\-\+\.]+);base64,(.*)$/; + if (context === "image") { + regex = /^data:(image\/[a-z]+);base64,(.*)$/; + } + // Split the base64 string into the MIME type and the base64 encoded data + if (!base64 || typeof base64 !== "string") { + logger.error(`${ + JSON.stringify({ + assistantId: assistantIds?.assistantId, + threadId: assistantIds?.threadId, + context: context, + error: "Expected a base64 string, typeof base64 is not string", + }) + }`); + throw new Error("Expected a base64 string"); + } + const parts = base64.match(regex); + if (!parts || parts.length !== 3) { + logger.error(`${ + JSON.stringify({ + assistantId: assistantIds?.assistantId, + threadId: assistantIds?.threadId, + context: context, + error: `${context} Base64 string is not properly formatted: ${base64}`, + }) + }`); + throw new Error( + `${context} Base64 string is not properly formatted: ${parts}`, + ); + } + const mimeType = parts[1]; // e.g., 'audio/png' or 'video/mp4' or 'audio/mp3' or 'image/png' + const mediaData = parts[2]; + // Convert the base64 encoded data to a binary string + const binaryStr = atob(mediaData); + // Convert the binary string to an array of bytes (Uint8Array) + const length = binaryStr.length; + const arrayBuffer = new Uint8Array(new ArrayBuffer(length)); + for (let i = 0; i < length; i++) { + arrayBuffer[i] = binaryStr.charCodeAt(i); + } + // Create and return the Blob object + return new Blob([arrayBuffer], { type: mimeType }); +} diff --git a/algolia/README.md b/algolia/README.md new file mode 100644 index 000000000..b20a965d8 --- /dev/null +++ b/algolia/README.md @@ -0,0 +1,44 @@ + +Loaders, actions and workflows for adding Agolia search, the leader in globally scalable, secure, digital search and discovery experiences that are ultrafast and reliable, to your deco.cx website. + +Algolia is a general purpose indexer. This means you can save any Json document and later retrieve it using the Search API. Although being a simpler solution than competing alternatives like Elastic Search or Solar, setting up an index on Algolia still requires some software engineering, like setting up searchable fields, facet fields and sorting indices. Hopefully, deco.cx introduces canonical types, like Product, ProductGroup, etc. These schemas allow this app to built solutions for common use-cases, like indexing Products for a Product Listing Page. + +# Installation +1. Install via decohub +2. Open your Algolia dashboard and grab your keys at settings > API Keys +3. Copy & Paste your Application ID and Admin API Key +4. Save & Publish this block +5. Click on the button below to setup the necessary indices on Algolia + +
+
+ +
+
+ + +> Use `Admin API Key` instead of `Search-Only API Key` since this app tweaks search params and create/deletes records + +## Integrating to Algolia +This App uses deco.cx canonical types in a push-based architecture. This means anyone interested in indexing any supported canonical type just need to invoke the right workflow passing the right input payload. Below you can see the schematics of how ecommerce platforms use the `workflows/index/product.ts` workflow for indexing products +Screenshot 2023-09-20 at 17 44 33 + +As you can see, this App already receives the `Product` as input parameter, so it's up to the ecommerce platform integration to invoke the workflow. +If you want to use Algolia with a supported ecommerce platform (VTEX, VNDA, Wake etc), install the ecommerce platform app and register Algolia workflow to listen to product events. +If you want to implement a new platform integration, you can base yourself on existing trigger implementation at VTEX/workflows/events.ts + +### VTEX Integration +To integrate with VTEX: + +1. Open your admin at deco.cx +2. Under Blocks > Apps, make sure both VTEX and Algolia apps are installed +3. Connect a product update event from VTEX into Algolia. For this: +0. 1. Under Blocks > Workflows -> events.ts open your VTEX-trigger block. +0. 2. Connect `Product` event from VTEX into Algolia by clicking on `+ Add Product` and selecting `Algolia Integration - Product Event` +0. 3. Save & Publish + +image + +🎉 Your setup is complete! You should now be able to see the products being indexed on Algolia. + +After indexing is complete, you can open your Pages at deco.cx and change loaders to Algolia Loaders diff --git a/algolia/actions/index/product.ts b/algolia/actions/index/product.ts new file mode 100644 index 000000000..cb266d05c --- /dev/null +++ b/algolia/actions/index/product.ts @@ -0,0 +1,43 @@ +import { ApiError } from "npm:@algolia/transporter@4.20.0"; +import { Product } from "../../../commerce/types.ts"; +import { AppContext } from "../../mod.ts"; +import { toIndex } from "../../utils/product.ts"; +import { INDEX_NAME } from "../../loaders/product/list.ts"; + +interface Props { + product: Product; + action: "DELETE" | "UPSERT"; + indexName?: string; +} + +// deno-lint-ignore no-explicit-any +const isAPIError = (x: any): x is ApiError => + typeof x?.status === "number" && + x.name === "ApiError"; + +const action = async (props: Props, _req: Request, ctx: AppContext) => { + const { indexName = INDEX_NAME, product, action } = props; + const { client } = ctx; + + try { + const indexProduct = toIndex(product); + + const { taskID } = action === "UPSERT" + ? await client.initIndex(indexName).saveObject(indexProduct, { + autoGenerateObjectIDIfNotExist: false, + }) + : await client.initIndex(indexName).deleteObject(indexProduct.objectID); + + return taskID; + } catch (error) { + console.error(error); + + if (isAPIError(error) && error.status === 400) { + return null; + } + + throw error; + } +}; + +export default action; diff --git a/algolia/actions/index/wait.ts b/algolia/actions/index/wait.ts new file mode 100644 index 000000000..31d4a2336 --- /dev/null +++ b/algolia/actions/index/wait.ts @@ -0,0 +1,16 @@ +import { INDEX_NAME } from "../../loaders/product/list.ts"; +import { AppContext } from "../../mod.ts"; + +interface Props { + taskID: number; + indexName?: string; +} + +const action = async (props: Props, _req: Request, ctx: AppContext) => { + const { client } = ctx; + const { indexName = INDEX_NAME } = props; + + await client.initIndex(indexName).waitTask(props.taskID); +}; + +export default action; diff --git a/algolia/actions/setup.ts b/algolia/actions/setup.ts new file mode 100644 index 000000000..f9900f5bc --- /dev/null +++ b/algolia/actions/setup.ts @@ -0,0 +1,23 @@ +import { AppContext } from "../mod.ts"; +import { setupProductsIndices } from "../utils/product.ts"; + +type Props = Partial<{ + adminApiKey: string; + applicationId: string; +}>; + +const action = async (props: Props, _req: Request, ctx: AppContext) => { + const { adminApiKey, applicationId } = props; + if (!adminApiKey || !applicationId) { + return "Missing Keys/AppId"; + } + + try { + await setupProductsIndices(applicationId, adminApiKey, ctx.client); + return "Setup finished successfuly"; + } catch (error) { + return `Setup finished with error: ${error}`; + } +}; + +export default action; diff --git a/algolia/loaders/client.ts b/algolia/loaders/client.ts new file mode 100644 index 000000000..65cef149b --- /dev/null +++ b/algolia/loaders/client.ts @@ -0,0 +1,14 @@ +import type { AppContext } from "../mod.ts"; +import type { SearchClient } from "https://esm.sh/algoliasearch@4.20.0"; + +export type AlgoliaClient = SearchClient; + +export default function loader( + _props: unknown, + _req: Request, + ctx: AppContext, +): AlgoliaClient { + const { client } = ctx; + + return client; +} diff --git a/algolia/loaders/product/list.ts b/algolia/loaders/product/list.ts new file mode 100644 index 000000000..766e706f4 --- /dev/null +++ b/algolia/loaders/product/list.ts @@ -0,0 +1,63 @@ +import { SearchResponse } from "npm:@algolia/client-search"; +import { Product } from "../../../commerce/types.ts"; + +import { AppContext } from "../../mod.ts"; +import { + IndexedProduct, + Indices, + resolveProducts, +} from "../../utils/product.ts"; + +interface Props { + /** + * @title Count + * @description Max number of products to return + */ + hitsPerPage: number; + + /** + * @title Facets + * @description Facets to filter by + */ + facetFilters?: string; + + /** @description Full text search query */ + term?: string; + indexName?: string; +} + +export const INDEX_NAME: Indices = "products"; + +/** + * @title Algolia Integration + */ +const loader = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { client } = ctx; + const { indexName = INDEX_NAME } = props; + + const { results } = await client.search([{ + indexName, + query: props.term ?? "", + params: { + hitsPerPage: props.hitsPerPage ?? 12, + facetFilters: JSON.parse(props.facetFilters ?? "[]"), + clickAnalytics: true, + }, + }]); + + const { hits: products, queryID } = results[0] as SearchResponse< + IndexedProduct + >; + + return resolveProducts(products, client, { + url: req.url, + queryID, + indexName, + }); +}; + +export default loader; diff --git a/algolia/loaders/product/listingPage.ts b/algolia/loaders/product/listingPage.ts new file mode 100644 index 000000000..30bc32186 --- /dev/null +++ b/algolia/loaders/product/listingPage.ts @@ -0,0 +1,280 @@ +import { SearchResponse } from "npm:@algolia/client-search"; +import { Filter, ProductListingPage } from "../../../commerce/types.ts"; +import { AppContext } from "../../mod.ts"; +import { replaceHighlight } from "../../utils/highlight.ts"; +import { + IndexedProduct, + Indices, + resolveProducts, +} from "../../utils/product.ts"; + +/** @titleBy label */ +interface Facet { + /** + * @title Facet Name + * @description These are the facet names available at Algolia dashboard > search > index */ + name: string; + + /** @description Facet label to be rendered on the site UI */ + label: string; +} + +interface Props { + /** + * @title Count + * @description Max number of products to return + */ + hitsPerPage: number; + + /** + * @title Facets + * @description List of facet names from Product to render on the website + */ + facets?: Facet[]; + + /** + * @description https://www.algolia.com/doc/api-reference/api-parameters/sortFacetValuesBy/ + */ + sortFacetValuesBy?: "count" | "alpha"; + + /** @description Full text search query */ + term?: string; + + /** @description Enable to highlight matched terms */ + highlight?: boolean; + + /** @description Hide Unavailable Items */ + hideUnavailable?: boolean; + + /** + * @description ?page search params for the first page + * @default 0 + */ + startingPage?: 0 | 1; +} + +const getPageInfo = ( + page: number, + nbPages: number, + nbHits: number, + hitsPerPage: number, + url: URL, + startingPage: number, +) => { + const next = page + 1; + const prev = page - 1; + const hasNextPage = next < nbPages; + const hasPreviousPage = prev >= 0; + const nextPage = new URLSearchParams(url.searchParams); + const previousPage = new URLSearchParams(url.searchParams); + + if (hasNextPage) { + nextPage.set("page", `${next + startingPage}`); + } + + if (hasPreviousPage) { + previousPage.set("page", `${prev + startingPage}`); + } + + return { + nextPage: hasNextPage ? `?${nextPage}` : undefined, + previousPage: hasPreviousPage ? `?${previousPage}` : undefined, + records: nbHits, + recordPerPage: hitsPerPage, + currentPage: page + startingPage, + }; +}; + +// Transforms facets and re-orders so they match what's configured on deco admin +const transformFacets = ( + facets: Record>, + options: { order: Facet[]; facetFilters: [string, string[]][]; url: URL }, +): Filter[] => { + const { facetFilters, url, order } = options; + const params = new URLSearchParams(url.searchParams); + const filters = Object.fromEntries(facetFilters); + const orderByKey = new Map( + order.map(({ name, label }, index) => [name, { label, index }]), + ); + const entries = Object.entries(facets); + + const transformed: Filter[] = new Array(entries.length); + for (let it = 0; it < entries.length; it++) { + const [key, values] = entries[it]; + const filter = filters[key] ?? []; + let index: number | undefined = it; + let label: string | undefined = key; + + // Apply sort only when user set facets on deco admin + if (orderByKey.size > 0) { + index = orderByKey.get(key)?.index; + label = orderByKey.get(key)?.label; + } + + if (index === undefined || label === undefined) continue; + + transformed[index] = { + "@type": "FilterToggle", + quantity: 0, + label, + key, + values: Object.entries(values).map(([value, quantity]) => { + const index = filter.findIndex((f) => f === value); + const selected = index > -1; + const newFilter = selected + ? { + ...filters, + [key]: [...filter].filter((f) => f !== value), + } + : { + ...filters, + [key]: [...filter, value], + }; + + if (newFilter[key].length === 0) { + delete newFilter[key]; + } + + params.set("facetFilters", JSON.stringify(Object.entries(newFilter))); + + return { + value, + quantity, + label: value, + selected, + url: `?${params}`, + }; + }), + }; + } + + return transformed.filter(Boolean); +}; + +const getIndex = (options: string | null): Indices => { + switch (options) { + case "relevance": + return "products"; + case "price_asc": + return "products_price_asc"; + case "price_desc": + return "products_price_desc"; + default: + return "products"; + } +}; + +/** + * @title Algolia Integration + */ +const loader = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const url = new URL(req.url); + const { client } = ctx; + const indexName = getIndex(url.searchParams.get("sort")); + const startingPage = props.startingPage ?? 0; + const pageIndex = Number(url.searchParams.get("page")) || startingPage; + + const facetFilters: [string, string[]][] = JSON.parse( + url.searchParams.get("facetFilters") ?? "[]", + ); + + if (props.hideUnavailable) { + facetFilters.push(["available", ["true"]]); + } + + // Creates a canonical facet representation format + // Facets on the same category are grouped by OR and facets on + // different categories are split by an AND. e.g.: + // + // (department:"man" OR department:"woman") AND (brand:"deco") AND (available:"true") + const fFilters = facetFilters.map(([key, values]) => + `(${values.map((value) => `${key}:"${value}"`).join(" OR ")})` + ).join(" AND "); + + const { results } = await client.search([ + { + indexName, + query: props.term ?? url.searchParams.get("q") ?? + url.searchParams.get("query") ?? "", + params: { + filters: fFilters, + facets: [], + hitsPerPage: props.hitsPerPage ?? 12, + page: pageIndex - startingPage, + clickAnalytics: true, + }, + }, + { + indexName, + query: props.term ?? url.searchParams.get("q") ?? + url.searchParams.get("query") ?? "", + params: { + facetingAfterDistinct: true, + facets: (props.facets?.length || 0) > 0 + ? props.facets?.map((f) => f.name) + : ["*"], + hitsPerPage: 0, + sortFacetValuesBy: props.sortFacetValuesBy, + }, + }, + ]); + + const [ + { hits, page = 0, nbPages = 1, queryID, nbHits = 12, hitsPerPage = 12 }, + { facets }, + ] = results as SearchResponse[]; + + const products = await resolveProducts( + hits.map(({ _highlightResult, ...p }) => + replaceHighlight(p, props.highlight ? _highlightResult : {}) + ), + client, + { url, queryID, indexName }, + ); + const pageInfo = getPageInfo( + page, + nbPages, + nbHits, + hitsPerPage, + url, + startingPage, + ); + const filters = transformFacets(facets ?? {}, { + order: props.facets ?? [], + facetFilters, + url, + }); + + return { + "@type": "ProductListingPage", + // TODO: Find out what's the right breadcrumb on algolia + breadcrumb: { + "@type": "BreadcrumbList", + itemListElement: [], + numberOfItems: 0, + }, + filters, + products, + pageInfo, + sortOptions: [ + { + value: "relevance", + label: "Relevance", + }, + { + value: "price_asc", + label: "Price - Lower to Higher", + }, + { + value: "price_desc", + label: "Price - Higher to Lower", + }, + ], + }; +}; + +export default loader; diff --git a/algolia/loaders/product/suggestions.ts b/algolia/loaders/product/suggestions.ts new file mode 100644 index 000000000..5bf3ce274 --- /dev/null +++ b/algolia/loaders/product/suggestions.ts @@ -0,0 +1,106 @@ +import type { SearchResponse } from "npm:@algolia/client-search"; +import { Suggestion } from "../../../commerce/types.ts"; +import type { AppContext } from "../../mod.ts"; +import { replaceHighlight } from "../../utils/highlight.ts"; +import { + type IndexedProduct, + Indices, + resolveProducts, +} from "../../utils/product.ts"; + +interface Props { + query?: string; + + /** @description number of suggested terms/products to return */ + count?: number; + + /** @description Enable to highlight matched terms */ + highlight?: boolean; + + /** @description Hide Unavailable Items */ + hideUnavailable?: boolean; +} + +interface IndexedSuggestion { + nb_words: number; + popularity: number; + products: { + exact_nb_hits: number; + facets: { + exact_matches: Record; + analytics: Record; + }; + }; + query: string; +} + +const toFacets = ( + facets: IndexedSuggestion["products"]["facets"]["exact_matches"], +) => + Object.entries(facets).map(([key, values]) => ({ + values: values.map((v) => v.value), + key, + })); + +const productsIndex = "products" satisfies Indices; + +/** + * @title Algolia Integration + */ +const loader = async ( + { query, count, highlight, hideUnavailable }: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { client } = ctx; + + const { results } = await client.search([ + { + indexName: "products_query_suggestions" satisfies Indices, + params: { hitsPerPage: count ?? 0 }, + query, + }, + { + indexName: productsIndex, + params: { + hitsPerPage: count ?? 0, + filters: hideUnavailable ? `available:true` : "", + facets: [], + clickAnalytics: true, + }, + query, + }, + ]); + + const [ + { hits: suggestions }, + { hits: indexedProducts, queryID }, + ] = results as [ + SearchResponse, + SearchResponse, + ]; + + const products = await resolveProducts( + indexedProducts.map(({ _highlightResult, ...p }) => + replaceHighlight(p, highlight ? _highlightResult : {}) + ), + client, + { url: req.url, queryID, indexName: productsIndex }, + ); + + const searches = suggestions.map((s) => ({ + term: s.query, + hits: s.products.exact_nb_hits, + facets: [ + ...toFacets(s.products.facets.exact_matches), + ...toFacets(s.products.facets.analytics), + ].filter(Boolean), + })); + + return { + searches: searches, + products, + }; +}; + +export default loader; diff --git a/algolia/logo.png b/algolia/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..25734ef10ca1bdb8570526b215e46d61002a4b1a GIT binary patch literal 10097 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 T6=_;J00000NkvXXu0mjf?zcN= literal 0 HcmV?d00001 diff --git a/algolia/manifest.gen.ts b/algolia/manifest.gen.ts new file mode 100644 index 000000000..10575358f --- /dev/null +++ b/algolia/manifest.gen.ts @@ -0,0 +1,39 @@ +// DO NOT EDIT. This file is generated by deco. +// This file SHOULD be checked into source version control. +// This file is automatically updated during development when running `dev.ts`. + +import * as $$$$$$$$$0 from "./actions/index/product.ts"; +import * as $$$$$$$$$1 from "./actions/index/wait.ts"; +import * as $$$$$$$$$2 from "./actions/setup.ts"; +import * as $$$0 from "./loaders/client.ts"; +import * as $$$1 from "./loaders/product/list.ts"; +import * as $$$2 from "./loaders/product/listingPage.ts"; +import * as $$$3 from "./loaders/product/suggestions.ts"; +import * as $$$$$$0 from "./sections/Analytics/Algolia.tsx"; +import * as $$$$$$$$$$0 from "./workflows/index/product.ts"; + +const manifest = { + "loaders": { + "algolia/loaders/client.ts": $$$0, + "algolia/loaders/product/list.ts": $$$1, + "algolia/loaders/product/listingPage.ts": $$$2, + "algolia/loaders/product/suggestions.ts": $$$3, + }, + "sections": { + "algolia/sections/Analytics/Algolia.tsx": $$$$$$0, + }, + "actions": { + "algolia/actions/index/product.ts": $$$$$$$$$0, + "algolia/actions/index/wait.ts": $$$$$$$$$1, + "algolia/actions/setup.ts": $$$$$$$$$2, + }, + "workflows": { + "algolia/workflows/index/product.ts": $$$$$$$$$$0, + }, + "name": "algolia", + "baseUrl": import.meta.url, +}; + +export type Manifest = typeof manifest; + +export default manifest; diff --git a/algolia/mod.ts b/algolia/mod.ts new file mode 100644 index 000000000..e607bcef2 --- /dev/null +++ b/algolia/mod.ts @@ -0,0 +1,88 @@ +import algolia from "https://esm.sh/algoliasearch@4.20.0"; +import { createFetchRequester } from "npm:@algolia/requester-fetch@4.20.0"; +import { Markdown } from "../decohub/components/Markdown.tsx"; +import { PreviewContainer } from "../utils/preview.tsx"; +import type { Secret } from "../website/loaders/secret.ts"; +import manifest, { Manifest } from "./manifest.gen.ts"; +import { type App, type AppContext as AC } from "@deco/deco"; +export type AppContext = AC>; +export interface State { + /** + * @title Your Algolia App ID + * @description https://dashboard.algolia.com/account/api-keys/all + */ + applicationId: string; + /** + * @title Search API Key + * @description https://dashboard.algolia.com/account/api-keys/all + * @format password + */ + searchApiKey: string; + /** + * @title Admin API Key + * @description https://dashboard.algolia.com/account/api-keys/all + * @format password + */ + adminApiKey: Secret; +} +/** + * @title Algolia + * @description Product search & discovery that increases conversions at scale. + * @category Search + * @logo https://raw.githubusercontent.com/deco-cx/apps/main/algolia/logo.png + */ +export default function App(props: State) { + const { applicationId, adminApiKey, searchApiKey } = props; + if (!adminApiKey) { + throw new Error("Missing admin API key"); + } + const stringAdminApiKey = typeof adminApiKey === "string" + ? adminApiKey + : adminApiKey?.get?.() ?? ""; + const client = algolia(applicationId, stringAdminApiKey, { + requester: createFetchRequester(), // Fetch makes it perform mutch better + }); + const state = { client, applicationId, searchApiKey }; + const app: App = { + manifest: { + ...manifest, + actions: { + ...manifest.actions, + "algolia/actions/setup.ts": { + ...manifest.actions["algolia/actions/setup.ts"], + default: (p, req, ctx) => + manifest.actions["algolia/actions/setup.ts"].default( + { applicationId, adminApiKey: stringAdminApiKey, ...p }, + req, + ctx, + ), + }, + }, + }, + state, + }; + return app; +} +export const preview = async () => { + const markdownContent = await Markdown( + new URL("./README.md", import.meta.url).href, + ); + return { + Component: PreviewContainer, + props: { + name: "Algolia", + owner: "deco.cx", + description: + "Product search & discovery that increases conversions at scale.", + logo: + "https://raw.githubusercontent.com/deco-cx/apps/main/algolia/logo.png", + images: [], + tabs: [ + { + title: "About", + content: markdownContent(), + }, + ], + }, + }; +}; diff --git a/algolia/sections/Analytics/Algolia.tsx b/algolia/sections/Analytics/Algolia.tsx new file mode 100644 index 000000000..d752c3038 --- /dev/null +++ b/algolia/sections/Analytics/Algolia.tsx @@ -0,0 +1,179 @@ +import insights from "npm:search-insights@2.9.0"; +import { + AddToCartEvent, + SelectItemEvent, + ViewItemEvent, + ViewItemListEvent, +} from "../../../commerce/types.ts"; +import { AppContext } from "../../mod.ts"; +import { type SectionProps } from "@deco/deco"; +import { useScriptAsDataURI } from "@deco/deco/hooks"; +declare global { + interface Window { + aa: typeof insights.default; + } +} +const setupAndListen = (appId: string, apiKey: string, version: string) => { + function setupScriptTag() { + globalThis.window.AlgoliaAnalyticsObject = "aa"; + globalThis.window.aa = globalThis.window.aa || + function () { + // @ts-expect-error monkey patch before initialization + (globalThis.window.aa.queue = globalThis.window.aa.queue || []).push( + arguments, + ); + }; + globalThis.window.aa.version = version; + const script = document.createElement("script"); + script.setAttribute("async", ""); + script.setAttribute( + "src", + `https://cdn.jsdelivr.net/npm/search-insights@${version}/dist/search-insights.min.js`, + ); + document.head.appendChild(script); + } + function createUserToken() { + if ( + typeof crypto !== "undefined" && + typeof crypto.randomUUID === "function" + ) { + return crypto.randomUUID(); + } + return (Math.random() * 1e9).toFixed(); + } + function setupSession() { + globalThis.window.aa("init", { appId, apiKey }); + const userToken = localStorage.getItem("ALGOLIA_USER_TOKEN") || + createUserToken(); + localStorage.setItem("ALGOLIA_USER_TOKEN", userToken); + globalThis.window.aa("setUserToken", userToken); + } + function setupEventListeners() { + function attributesFromURL(href: string) { + const url = new URL(href); + const queryID = url.searchParams.get("algoliaQueryID"); + const indexName = url.searchParams.get("algoliaIndex"); + // Not comming from an algolia search page + if (!queryID || !indexName) { + return null; + } + return { queryID, indexName }; + } + // deno-lint-ignore no-explicit-any + function isSelectItemEvent(event: any): event is SelectItemEvent { + return event.name === "select_item"; + } + // deno-lint-ignore no-explicit-any + function isAddToCartEvent(event: any): event is AddToCartEvent { + return event.name === "add_to_cart"; + } + function isViewItem( + // deno-lint-ignore no-explicit-any + event: any, + ): event is ViewItemEvent | ViewItemListEvent { + return event.name === "view_item" || event.name === "view_item_list"; + } + type WithID = T & { + item_id: string; + }; + const hasItemId = (item: T): item is WithID => + // deno-lint-ignore no-explicit-any + typeof (item as any).item_id === "string"; + const PRODUCTS = "products"; + const MAX_BATCH_SIZE = 20; + globalThis.window.DECO.events.subscribe((event) => { + if (!event) { + return; + } + const eventName = event.name; + if (isSelectItemEvent(event)) { + const [item] = event.params.items; + if ( + !item || + !hasItemId(item) || + typeof item.index !== "number" || + typeof item.item_url !== "string" + ) { + return console.warn( + "Failed sending event to Algolia. Missing index, item_id or item_url", + JSON.stringify(event, null, 2), + ); + } + const attr = attributesFromURL(item.item_url); + if (attr) { + globalThis.window.aa("clickedObjectIDsAfterSearch", { + eventName, + index: attr.indexName, + queryID: attr.queryID, + objectIDs: [item.item_id], + positions: [item.index + 1], + }); + } else { + globalThis.window.aa("clickedObjectIDs", { + eventName, + index: PRODUCTS, + objectIDs: [item.item_id], + }); + } + } + if (isAddToCartEvent(event)) { + const [item] = event.params.items; + const attr = attributesFromURL(globalThis.window.location.href) || + attributesFromURL(item.item_url || ""); + const objectIDs = event.params.items + .filter(hasItemId) + .map((i) => i.item_id); + if (attr) { + globalThis.window.aa("convertedObjectIDsAfterSearch", { + eventName, + objectIDs, + index: attr.indexName, + queryID: attr.queryID, + }); + } else { + globalThis.window.aa("convertedObjectIDs", { + eventName, + index: PRODUCTS, + objectIDs, + }); + } + } + if (isViewItem(event)) { + const objectIDs = event.params.items + .filter(hasItemId) + .map((i) => i.item_id); + for (let it = 0; it < objectIDs.length; it += MAX_BATCH_SIZE) { + globalThis.window.aa("viewedObjectIDs", { + eventName, + index: PRODUCTS, + objectIDs: objectIDs.slice(it, (it + 1) * MAX_BATCH_SIZE), + }); + } + } + }); + } + setupScriptTag(); + setupSession(); + setupEventListeners(); +}; +function Analytics( + { applicationId, searchApiKey }: SectionProps, +) { + return ( + `; + const flagsScript = ``; + return dnsPrefetchLink + preconnectLink + plausibleScript + flagsScript; + }; + return ({ src: transformReq }); +}; +export default loader; diff --git a/analytics/logo.png b/analytics/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a617992e1e60c1f404adeb4abe037fe112717106 GIT binary patch literal 1920 zcmbuAXH?T^7RK|ZDCMBiq)HRWQUg+*fb=2+LPuaAL5ky00s%oFVAetx#vsiMW5xj# zm1HR)AP|a(N);S>0BIpGf`l3fBpbiZ?m7Em&w1|q+~0fOPw%<++#C;gCovIu5fBI@ zcGcP5i|2&zA#{p&<_+J_85^A^V>l{0#Zjt7nXpq)VR_ znPMvg!xQhm_(6(bTH^Dj0pECCLdE&LYn0%p7Bp@0#uO&It%CXWw?p%_7ipUrt;t+v zq@sqBO(%zqpKWO|!qp|TlGkn8I-;W!XY)v@k6va!l`-_S`6tVfg&H+h6Z^K(u~2s< zOlPlsD$wpyxH^CF>{tYo&JW(Sv(GlrV_g#p>wEOVU8}B;$CvN9-R(emxL%-P z9-LaV=7jy=_<7Lpe9xaxvhIu+0(Ka)oU?LIDA3vht=q;x3)9;Dhw3jQ0v6dz5sP+b zc)2Hbt}htaMlRDAN^fG<(#)Ip?K0>ygC|miWO^p(GP({^X3@lNtf=oW^!iXG#bBeo zV_XzK(_s-^O3mk;Y6)s0P{??~_*s`WmP1s2i$0bQg&9j7-WN@jHSB0?I|5942ZF9x z2&HyQ=wj9Q?rZ-qz06hdFRn#})1M+-HIBnNrXR&;lR|K;X^oV<_pcU-11{yVtqtpf z3C6pzNMHb>|MJ(0&-$=b$bk$p{;+;#a0-s7oWMIcVdbFD&2u6-OtRDkUX|XwOC@fTxUM7O z$lhm=T|QU|_gR>-$5b+x)1JRrF@GG~moF^jEASus%1_nI+etOQ0C3bvJE;tNERLIE zskkKXv28`l3iDPbjHg&Ox}{@RUC`*;UWPzGsxOb8tnPIN$H6~mgg~Nbf_Bn??ce0P zwhvq;nFY8=s;us5dTa45x6LhUzccwx{tNs(3fIA4f1SWbzk7<+ZI`Uw0?;9-`eKd+ z??vB3e_KK>==c9qeUlZw5W!w{l9SOoySl-TCCnfVh-6A4(wuUMTY%E>W{ZTiIr})PS=X7*5=)VZth1W+4U}`#~)I~9qJ=U z0Y{=n^(Tx8Soan6A{2Y1brDjsbZ=OsLu}{G^2M~_lZ_U8Zkkrf@EO>lP6B#srXRgE zx3Y_?g1N}3Xo`~VTmS9P{ww+leKHg92$lD1yD38GVpp5=8Vh)Qd3Wb#`wl{c(3xEm zbdkvG~yEDeZN-L3Sv#4?niBp<&-WS+W}}~J`H+&^bczaV(N>p_%cDxwK0X2 zwIs}H2rSad9=M#M-9YYLP=>m{H4uDM8@D0zpx2#YQBVx)lKjiaztE5A-y@3Lh3{6^3$+#Vc;tCC&1PE&CG_PclCa#nnc z_~x}{o!+0?XbA~tP2Jf|Ys@8ylepC!3UAEsp*c0JTga6d_?jHDJC+pt;D)})r;a!H zAjJA!C3Q}Eb%+F!o5*0wGPKGu={=7GxL8Apk2houMeQ$s0!G2q{$z?Hfg15g_T^zUch~11m=Z=kH{v>vd@LT z@xi|&Slj*-+O45HhQows#uKkdR!q?p7JILsv*L594))9R`KHZVn4qf;?)F4m>; +// deno-lint-ignore no-explicit-any +export type State = any; +/** + * @title Deco Analytics + * @description Measure your site traffic at a glance in a simple and modern web analytics dashboard. + * @category Analytics + * @logo https://raw.githubusercontent.com/deco-cx/apps/main/analytics/logo.png + */ +export default function App(state: State): App { + return { manifest, state }; +} +export const preview = () => { + return { + Component: PreviewContainer, + props: { + name: "Deco Analytics", + owner: "deco.cx", + description: + "Measure your site traffic at a glance in a simple and modern web analytics dashboard.", + logo: + "https://raw.githubusercontent.com/deco-cx/apps/main/analytics/logo.png", + images: [], + tabs: [], + }, + }; +}; diff --git a/analytics/scripts/plausible_scripts.ts b/analytics/scripts/plausible_scripts.ts new file mode 100644 index 000000000..c96269fab --- /dev/null +++ b/analytics/scripts/plausible_scripts.ts @@ -0,0 +1,24 @@ +// We got these scripts from plausible. Here is the docs for every possible extension: +// https://plausible.io/docs/script-extensions + +// The scripts currently have extensions '.local', '.hash' and '.exclusions'. + +// It is necessary to change how we get data-domain: +// +// ((w,d)=>{const h=w.location.hostname;return h.replace(/^www./,"")})(window,document) +// +// We can avoid setting data-domain per site using this strategy. +// Also, when copying the script from plausible, remember to re-escape \ on regex calls. +// The difference between localAndExclusionAndHashScript and exclusionAndHashScript is the parameter '.local' +// when generating the script. + +// For localhost debug only +export const localAndExclusionAndHashScript = + '!function(){"use strict";var c=window.location,o=window.document,u=o.currentScript,s=u.getAttribute("data-api")||new URL(u.src).origin+"/api/event";function p(e,t){e&&console.warn("Ignoring Event: "+e),t&&t.callback&&t.callback()}function e(e,t){try{if("true"===window.localStorage.plausible_ignore)return p("localStorage flag",t)}catch(e){}var i=u&&u.getAttribute("data-include"),n=u&&u.getAttribute("data-exclude");if("pageview"===e){i=!i||i.split(",").some(a),n=n&&n.split(",").some(a);if(!i||n)return p("exclusion rule",t)}function a(e){var t=c.pathname;return(t+=c.hash).match(new RegExp("^"+e.trim().replace(/\\*\\*/g,".*").replace(/([^\\.])\\*/g,"$1[^\\\\s/]*")+"/?$"))}var i={},n=(i.n=e,i.u=c.href,i.d=((w,d)=>{const h=w.location.hostname;return h.replace(/^www./,"")})(window,document),i.r=o.referrer||null,t&&t.meta&&(i.m=JSON.stringify(t.meta)),t&&t.props&&(i.p=t.props),u.getAttributeNames().filter(function(e){return"event-"===e.substring(0,6)})),r=i.p||{},l=(n.forEach(function(e){var t=e.replace("event-",""),e=u.getAttribute(e);r[t]=r[t]||e}),i.p=r,i.h=1,new XMLHttpRequest);l.open("POST",s,!0),l.setRequestHeader("Content-Type","text/plain"),l.send(JSON.stringify(i)),l.onreadystatechange=function(){4===l.readyState&&t&&t.callback&&t.callback()}}var t=window.plausible&&window.plausible.q||[];window.plausible=e;for(var i,n=0;n{const h=w.location.hostname;return h.replace(/^www./,"")})(window,document),n.r=c.referrer||null,t&&t.meta&&(n.m=JSON.stringify(t.meta)),t&&t.props&&(n.p=t.props),s.getAttributeNames().filter(function(e){return"event-"===e.substring(0,6)})),r=n.p||{},o=(i.forEach(function(e){var t=e.replace("event-",""),e=s.getAttribute(e);r[t]=r[t]||e}),n.p=r,n.h=1,new XMLHttpRequest);o.open("POST",u,!0),o.setRequestHeader("Content-Type","text/plain"),o.send(JSON.stringify(n)),o.onreadystatechange=function(){4===o.readyState&&t&&t.callback&&t.callback()}}var t=window.plausible&&window.plausible.q||[];window.plausible=e;for(var n,i=0;i; +} + +export const Preview = () => ( + + + ); +} diff --git a/deco.ts b/deco.ts index 42fd76176..48947b7b6 100644 --- a/deco.ts +++ b/deco.ts @@ -1,11 +1,53 @@ -const app = (name: string) => ({ dir: `./${name.replace("apps/", "")}`, name }); +const app = (name: string) => ({ dir: name, name }); + +const compatibilityApps = [{ + dir: "./compat/$live", + name: "$live", +}, { + dir: "./compat/std", + name: "deco-sites/std", +}]; const config = { apps: [ - app("apps/vtex"), - app("apps/vnda"), - app("apps/website"), - app("apps/workflows"), + app("posthog"), + app("decopilot-app"), + app("smarthint"), + app("ra-trustvox"), + app("anthropic"), + app("resend"), + app("emailjs"), + app("konfidency"), + app("mailchimp"), + app("ai-assistants"), + app("files"), + app("openai"), + app("brand-assistant"), + app("implementation"), + app("weather"), + app("blog"), + app("analytics"), + app("sourei"), + app("typesense"), + app("algolia"), + app("vtex"), + app("vnda"), + app("wake"), + app("wap"), + app("linx"), + app("linx-impulse"), + app("shopify"), + app("nuvemshop"), + app("website"), + app("commerce"), + app("workflows"), + app("verified-reviews"), + app("power-reviews"), + app("crux"), + app("decohub"), + app("htmx"), + app("sap"), + ...compatibilityApps, ], }; diff --git a/decohub/README.md b/decohub/README.md new file mode 100644 index 000000000..229dc7ab0 --- /dev/null +++ b/decohub/README.md @@ -0,0 +1,17 @@ +

+

+ + Deco + +

+

+ +

+ + Deco Hub + +

+

+ Unlock apps and integrations on deco.cx +

+ diff --git a/decohub/apps/ai-assistants.ts b/decohub/apps/ai-assistants.ts new file mode 100644 index 000000000..02d3e519d --- /dev/null +++ b/decohub/apps/ai-assistants.ts @@ -0,0 +1 @@ +export { default } from "../../ai-assistants/mod.ts"; diff --git a/decohub/apps/algolia.ts b/decohub/apps/algolia.ts new file mode 100644 index 000000000..780f10fc6 --- /dev/null +++ b/decohub/apps/algolia.ts @@ -0,0 +1 @@ +export { default, preview } from "../../algolia/mod.ts"; diff --git a/decohub/apps/analytics.ts b/decohub/apps/analytics.ts new file mode 100644 index 000000000..5cbe95e74 --- /dev/null +++ b/decohub/apps/analytics.ts @@ -0,0 +1 @@ +export { default } from "../../analytics/mod.ts"; diff --git a/decohub/apps/anthropic.ts b/decohub/apps/anthropic.ts new file mode 100644 index 000000000..125be545f --- /dev/null +++ b/decohub/apps/anthropic.ts @@ -0,0 +1 @@ +export { default } from "../../anthropic/mod.ts"; diff --git a/decohub/apps/blog.ts b/decohub/apps/blog.ts new file mode 100644 index 000000000..94a7db900 --- /dev/null +++ b/decohub/apps/blog.ts @@ -0,0 +1 @@ +export { default } from "../../blog/mod.ts"; diff --git a/decohub/apps/brand-assistant.ts b/decohub/apps/brand-assistant.ts new file mode 100644 index 000000000..0514804b5 --- /dev/null +++ b/decohub/apps/brand-assistant.ts @@ -0,0 +1 @@ +export { default } from "../../brand-assistant/mod.ts"; diff --git a/decohub/apps/crux.ts b/decohub/apps/crux.ts new file mode 100644 index 000000000..7d4d5531f --- /dev/null +++ b/decohub/apps/crux.ts @@ -0,0 +1 @@ +export { default, Preview } from "../../crux/mod.ts"; diff --git a/decohub/apps/emailjs.ts b/decohub/apps/emailjs.ts new file mode 100644 index 000000000..e43c37d38 --- /dev/null +++ b/decohub/apps/emailjs.ts @@ -0,0 +1 @@ +export { default, preview } from "../../emailjs/mod.ts"; diff --git a/decohub/apps/htmx.ts b/decohub/apps/htmx.ts new file mode 100644 index 000000000..b0f57112b --- /dev/null +++ b/decohub/apps/htmx.ts @@ -0,0 +1 @@ +export { default, preview } from "../../htmx/mod.ts"; diff --git a/decohub/apps/implementation.ts b/decohub/apps/implementation.ts new file mode 100644 index 000000000..ee33cec16 --- /dev/null +++ b/decohub/apps/implementation.ts @@ -0,0 +1 @@ +export { default, preview } from "../../implementation/mod.ts"; diff --git a/decohub/apps/konfidency.ts b/decohub/apps/konfidency.ts new file mode 100644 index 000000000..eff6eb05a --- /dev/null +++ b/decohub/apps/konfidency.ts @@ -0,0 +1 @@ +export { default, preview } from "../../konfidency/mod.ts"; diff --git a/decohub/apps/linx-impulse.ts b/decohub/apps/linx-impulse.ts new file mode 100644 index 000000000..9b70b2888 --- /dev/null +++ b/decohub/apps/linx-impulse.ts @@ -0,0 +1 @@ +export { default } from "../../linx-impulse/mod.ts"; diff --git a/decohub/apps/linx.ts b/decohub/apps/linx.ts new file mode 100644 index 000000000..4f983ceab --- /dev/null +++ b/decohub/apps/linx.ts @@ -0,0 +1 @@ +export { default, preview } from "../../linx/mod.ts"; diff --git a/decohub/apps/mailchimp.ts b/decohub/apps/mailchimp.ts new file mode 100644 index 000000000..a470fd9a3 --- /dev/null +++ b/decohub/apps/mailchimp.ts @@ -0,0 +1 @@ +export { default, preview } from "../../mailchimp/mod.ts"; diff --git a/decohub/apps/nuvemshop.ts b/decohub/apps/nuvemshop.ts new file mode 100644 index 000000000..ed6f5b479 --- /dev/null +++ b/decohub/apps/nuvemshop.ts @@ -0,0 +1 @@ +export { default, preview } from "../../nuvemshop/mod.ts"; diff --git a/decohub/apps/posthog.ts b/decohub/apps/posthog.ts new file mode 100644 index 000000000..c4cf74803 --- /dev/null +++ b/decohub/apps/posthog.ts @@ -0,0 +1 @@ +export { default, preview } from "../../posthog/mod.ts"; diff --git a/decohub/apps/power-reviews.ts b/decohub/apps/power-reviews.ts new file mode 100644 index 000000000..c7e62728c --- /dev/null +++ b/decohub/apps/power-reviews.ts @@ -0,0 +1 @@ +export { default } from "../../power-reviews/mod.ts"; diff --git a/decohub/apps/ra-trustvox.ts b/decohub/apps/ra-trustvox.ts new file mode 100644 index 000000000..488ad8448 --- /dev/null +++ b/decohub/apps/ra-trustvox.ts @@ -0,0 +1 @@ +export { default } from "../../ra-trustvox/mod.ts"; diff --git a/decohub/apps/resend.ts b/decohub/apps/resend.ts new file mode 100644 index 000000000..b85fea41e --- /dev/null +++ b/decohub/apps/resend.ts @@ -0,0 +1 @@ +export { default, preview } from "../../resend/mod.ts"; diff --git a/decohub/apps/shopify.ts b/decohub/apps/shopify.ts new file mode 100644 index 000000000..963ab1d19 --- /dev/null +++ b/decohub/apps/shopify.ts @@ -0,0 +1 @@ +export { default, preview } from "../../shopify/mod.ts"; diff --git a/decohub/apps/smarthint.ts b/decohub/apps/smarthint.ts new file mode 100644 index 000000000..0489d6d98 --- /dev/null +++ b/decohub/apps/smarthint.ts @@ -0,0 +1 @@ +export { default, preview } from "../../smarthint/mod.ts"; diff --git a/decohub/apps/sourei.ts b/decohub/apps/sourei.ts new file mode 100644 index 000000000..2186ea074 --- /dev/null +++ b/decohub/apps/sourei.ts @@ -0,0 +1 @@ +export { default, preview } from "../../sourei/mod.ts"; diff --git a/decohub/apps/typesense.ts b/decohub/apps/typesense.ts new file mode 100644 index 000000000..738685d25 --- /dev/null +++ b/decohub/apps/typesense.ts @@ -0,0 +1 @@ +export { default, preview } from "../../typesense/mod.ts"; diff --git a/decohub/apps/verified-reviews.ts b/decohub/apps/verified-reviews.ts new file mode 100644 index 000000000..e2e9da3bf --- /dev/null +++ b/decohub/apps/verified-reviews.ts @@ -0,0 +1 @@ +export { default } from "../../verified-reviews/mod.ts"; diff --git a/decohub/apps/vnda.ts b/decohub/apps/vnda.ts new file mode 100644 index 000000000..55f90c014 --- /dev/null +++ b/decohub/apps/vnda.ts @@ -0,0 +1 @@ +export { default, preview } from "../../vnda/mod.ts"; diff --git a/decohub/apps/vtex.ts b/decohub/apps/vtex.ts new file mode 100644 index 000000000..66a661a55 --- /dev/null +++ b/decohub/apps/vtex.ts @@ -0,0 +1,16 @@ +export { default } from "../../vtex/mod.ts"; +import { PreviewVtex } from "../../vtex/preview/Preview.tsx"; +import { Markdown } from "../components/Markdown.tsx"; +import { type AppRuntime } from "@deco/deco"; +export const preview = async (props: AppRuntime) => { + const markdownContent = await Markdown( + new URL("../../vtex/README.md", import.meta.url).href, + ); + return { + Component: PreviewVtex, + props: { + ...props, + markdownContent, + }, + }; +}; diff --git a/decohub/apps/wake.ts b/decohub/apps/wake.ts new file mode 100644 index 000000000..47cabd78c --- /dev/null +++ b/decohub/apps/wake.ts @@ -0,0 +1 @@ +export { default, preview } from "../../wake/mod.ts"; diff --git a/decohub/apps/wap.ts b/decohub/apps/wap.ts new file mode 100644 index 000000000..fd842f473 --- /dev/null +++ b/decohub/apps/wap.ts @@ -0,0 +1,3 @@ +import { Markdown as _Markdown } from "../components/Markdown.tsx"; + +export { default } from "../../wap/mod.ts"; diff --git a/decohub/apps/weather.ts b/decohub/apps/weather.ts new file mode 100644 index 000000000..2f174decb --- /dev/null +++ b/decohub/apps/weather.ts @@ -0,0 +1 @@ +export { default } from "../../weather/mod.ts"; diff --git a/decohub/apps/workflows.ts b/decohub/apps/workflows.ts new file mode 100644 index 000000000..ee27ff726 --- /dev/null +++ b/decohub/apps/workflows.ts @@ -0,0 +1 @@ +export { default } from "../../workflows/mod.ts"; diff --git a/decohub/components/Markdown.tsx b/decohub/components/Markdown.tsx new file mode 100644 index 000000000..1f590df3d --- /dev/null +++ b/decohub/components/Markdown.tsx @@ -0,0 +1,53 @@ +interface DenoGfm { + render: ( + content: string, + options: { + allowIframes: boolean; + allowMath: boolean; + disableHtmlSanitization: boolean; + }, + ) => string; + KATEX_CSS: string; + CSS: string; +} + +let denoGfm: Promise | null = null; +const importDenoGfm = async (): Promise => { + const gfmVersion = `0.9.0`; + try { + const gfm = await import(`jsr:@deno/gfm@${gfmVersion}`); + return gfm; + } catch (err) { + return { + render: () => `could not dynamic load @deno/gfm@${gfmVersion} ${err}`, + KATEX_CSS: "", + CSS: "", + }; + } +}; +export const Markdown = async (path: string) => { + denoGfm ??= importDenoGfm(); + const { CSS, KATEX_CSS, render } = await denoGfm; + const content = await fetch(path) + .then((res) => res.text()) + .catch(() => `Could not fetch README.md for ${path}`); + + return () => { + return ( + <> + + + ); +} diff --git a/power-reviews/sections/WriteReviewForm.tsx b/power-reviews/sections/WriteReviewForm.tsx new file mode 100644 index 000000000..11923a935 --- /dev/null +++ b/power-reviews/sections/WriteReviewForm.tsx @@ -0,0 +1,53 @@ +export interface Props { + /** + * @title App Key + * @ignore + */ + appKey?: string; + /** + * @title Locale + * @ignore + */ + locale?: string; + /** + * @title Merchant Id + * @ignore + */ + merchantId?: string; + /** + * @title Merchant Group + * @ignore + */ + merchantGroup?: string; +} + +export default function WriteReviewForm(state: Props) { + return ( +
+
+ + +
+ ); +} diff --git a/power-reviews/utils/client.ts b/power-reviews/utils/client.ts new file mode 100644 index 000000000..cc3617314 --- /dev/null +++ b/power-reviews/utils/client.ts @@ -0,0 +1,39 @@ +import { + ContextInformation, + PageReview, + ReviewForm, + ReviewFormField, +} from "./types.ts"; + +export interface PowerReviews { + "GET /m/:merchantId/l/:locale/product/:pageId/reviews": { + response: PageReview; + searchParams: { + _noconfig: string; + image_only: boolean; + sort?: string; + filters?: string; + "paging.from": number; + "paging.size": number; + }; + }; + + "GET /war/writereview": { + response: ReviewForm; + searchParams: { + "merchant_id": string; + "page_id": string; + }; + }; + + "POST /war/writereview": { + searchParams: { + "merchant_id": string; + "page_id": string; + }; + body: { + fields: ReviewFormField[]; + context_information: ContextInformation; + }; + }; +} diff --git a/power-reviews/utils/tranform.ts b/power-reviews/utils/tranform.ts new file mode 100644 index 000000000..be4a0fba3 --- /dev/null +++ b/power-reviews/utils/tranform.ts @@ -0,0 +1,77 @@ +import { Product } from "../../commerce/types.ts"; +import { Review, Rollup } from "./types.ts"; + +export const toReview = (review: Review) => { + const date = new Date(review.details.created_date); + const formatedDate = date.getFullYear() + "-" + + (date.getMonth() + 1) + "-" + (date.getDate()); + const pros = review.details.properties.find((prop) => prop.key == "pros") + ?.value; + const cons = review.details.properties.find((prop) => prop.key == "cons") + ?.value; + return { + "@type": "Review" as const, + id: review.internal_review_id.toString(), + author: [ + { + "@type": "Author" as const, + name: `${review.details.nickname}`, + verifiedBuyer: review.badges.is_verified_buyer, + location: review.details.location, + }, + ], + datePublished: formatedDate, + itemReviewed: review.details.product_name, + negativeNotes: cons, + positiveNotes: pros, + reviewHeadline: review.details.headline, + reviewBody: review.details.comments, + reviewRating: { + "@type": "AggregateRating" as const, + ratingValue: review.metrics.rating, + }, + tags: review.details.properties.map((props) => ({ + label: props.label, + value: props.value, + })), + brand: { + name: review.details.brand_name, + logo: review.details.brand_logo_uri, + url: review.details.brand_base_url, + }, + media: review.media?.map((media) => ({ + type: media.type, + url: media.uri, + alt: media.id, + likes: media.helpful_votes, + unlikes: media.not_helpful_votes, + })), + }; +}; + +export const toAggregateRating = (rollup: Rollup) => { + if (!rollup) { + return { + "@type": "AggregateRating" as const, + ratingValue: 0, + ratingCount: 0, + }; + } + return { + "@type": "AggregateRating" as const, + ratingValue: rollup?.average_rating || 0, + ratingCount: rollup?.review_count || 0, + }; +}; + +export const toPowerReviewId = (prop: string | undefined, product: Product) => { + if (prop == "sku") { + return product.sku; + } + + if (prop == "model") { + return product.isVariantOf?.model; + } + + return product.productID; +}; diff --git a/power-reviews/utils/types.ts b/power-reviews/utils/types.ts new file mode 100644 index 000000000..dfedcd651 --- /dev/null +++ b/power-reviews/utils/types.ts @@ -0,0 +1,214 @@ +export interface PageReview { + name: string; + paging: Paging; + results: Result[]; +} + +export interface Paging { + total_results: number; + pages_total: number; + page_size: number; + current_page_number: number; + next_page_url: string; +} + +export interface Result { + page_id: string; + rollup: Rollup; + reviews: Review[]; +} + +export interface Review { + ugc_id: number; + legacy_id: number; + review_id: number; + internal_review_id: number; + details: Details; + badges: Badges; + media: Media[]; + metrics: Metrics; +} + +export interface Badges { + is_staff_reviewer: boolean; + is_verified_buyer: boolean; + is_verified_reviewer: boolean; +} + +export interface Details { + comments: string; + headline: string; + nickname: string; + properties: Property[]; + product_name: string; + location: string; + created_date: number; + updated_date: number; + product_page_id: string; + brand_base_url: string; + brand_logo_uri: string; + brand_name: string; +} + +export interface Property { + key: string; + label: string; + type: string; + value: string[]; +} + +export interface Metrics { + helpful_votes: number; + not_helpful_votes: number; + rating: number; + helpful_score: number; +} + +export interface Rollup { + properties: Property[]; + rating_histogram: number[]; + recommended_ratio: number; + average_rating: number; + review_count: number; + media: Media[]; + name: string; + native_review_count: number; + syndicated_review_count: number; +} + +export interface Media { + id: string; + review_id: string; + uri: string; + headline: string; + rating: string; + helpful_votes: number; + not_helpful_votes: number; + type: "image" | "video"; + caption: string; + nickname: string; + created_date: number; +} + +export interface Property { + display_type: string; + key: string; + name: string; + type: string; + values: Value[]; + display_values: string[]; +} + +export interface Value { + label: string; + count: number; +} + +export interface ReviewForm { + merchant_information: MerchantInformation; + product_information: ProductInformation; + fields: ReviewFormField[]; + context_information: ContextInformation; +} + +export interface ContextInformation { + review_session_id: string; + review_start_date: Date; + product_id: number; + product_template_id: number; + image_template_id: number; + context_hash: number; +} + +export interface ReviewFormField { + id: string; + field_type: string; + key: string; + label?: string; + required?: boolean; + input_type?: string; + answer_type?: string; + max_length?: number; + helper_text?: string; + choices?: Choice[]; + prompt?: string; + composite_type?: string; + count?: number; + fields?: FieldField[]; + value?: string | number; +} + +export interface Choice { + id: string; + value: string; +} + +export interface FieldField { + id: string; + field_type: string; + key: string; + label: string; + required: boolean; + helper_text: string; + input_type: string; + answer_type: string; + choices?: Choice[]; +} + +export interface MerchantInformation { + name: string; + configuration: Configuration; + return_url: string; +} + +export interface Configuration { + is_live: boolean; + collect_email: string; + allow_facebook_connect: boolean; + allow_post_to_twitter: boolean; + enable_share_to_amazon: boolean; + enable_share_to_bestbuy: boolean; + enable_share_to_coolblue: boolean; + services_must_agree_with_terms: boolean; + video_collection_type: string; + star_styles: string; + enable_front_end_iovation_validation: boolean; + enable_enhanced_content_security: boolean; + allow_post_to_pinterest_war: boolean; + enable_facebook_integration: boolean; + enable_instagram_integration: boolean; + war_minimum_required_characters: number; + war_minimum_recommended_characters: number; + ryp_sort_order: string; + disable_cd4_heading_structures: boolean; + share_to_retailer_display: string; + social_measurement_data: string; + answerbox_enable_pre_question: boolean; + answerbox_nickname_is_required: boolean; + answerbox_required_email: boolean; + enable_rating_only_collection: boolean; +} + +export interface ProductInformation { + name: string; + full_product_image_urls: FullProductImageUrls; + full_product_url: string; + page_id: string; + locale: string; + product_lookup_location: string; +} + +export interface FullProductImageUrls { + "100": string; +} + +export interface WriteReviewResponse { + fields: ReviewFormField; + context_information: ContextInformation; + status_code: string; + progressive_info?: { + is_progressive_type: boolean; + is_last_stap: false; + current_step: number; + }; +} diff --git a/ra-trustvox/components/TrustvoxCertificateFixed.tsx b/ra-trustvox/components/TrustvoxCertificateFixed.tsx new file mode 100644 index 000000000..31cc3d608 --- /dev/null +++ b/ra-trustvox/components/TrustvoxCertificateFixed.tsx @@ -0,0 +1,5 @@ +export default function TrustvoxCertificateFixed() { + return ( +
+ ); +} diff --git a/ra-trustvox/components/TrustvoxProductDetailsRate.tsx b/ra-trustvox/components/TrustvoxProductDetailsRate.tsx new file mode 100644 index 000000000..3ac819313 --- /dev/null +++ b/ra-trustvox/components/TrustvoxProductDetailsRate.tsx @@ -0,0 +1,50 @@ +interface Props { + /** + * @ignore + */ + productId: string; +} + +export default function TrustvoxProductDetailsRate({ productId }: Props) { + return ( + <> + + +
+ Clique e veja! + + + ); +} diff --git a/ra-trustvox/components/TrustvoxShelfRate.tsx b/ra-trustvox/components/TrustvoxShelfRate.tsx new file mode 100644 index 000000000..3db21cc6a --- /dev/null +++ b/ra-trustvox/components/TrustvoxShelfRate.tsx @@ -0,0 +1,31 @@ +interface Props { + /** + * @ignore + */ + productId: string; +} + +export default function TrustvoxShelfRate({ productId }: Props) { + return ( + <> + +
+ + ); +} diff --git a/ra-trustvox/manifest.gen.ts b/ra-trustvox/manifest.gen.ts new file mode 100644 index 000000000..860c4dc36 --- /dev/null +++ b/ra-trustvox/manifest.gen.ts @@ -0,0 +1,23 @@ +// DO NOT EDIT. This file is generated by deco. +// This file SHOULD be checked into source version control. +// This file is automatically updated during development when running `dev.ts`. + +import * as $$$$$$0 from "./sections/TrustvoxCertificate.tsx"; +import * as $$$$$$1 from "./sections/TrustvoxProductReviews.tsx"; +import * as $$$$$$2 from "./sections/TrustvoxRateConfig.tsx"; +import * as $$$$$$3 from "./sections/TrustvoxStoreReviewsCarousel.tsx"; + +const manifest = { + "sections": { + "ra-trustvox/sections/TrustvoxCertificate.tsx": $$$$$$0, + "ra-trustvox/sections/TrustvoxProductReviews.tsx": $$$$$$1, + "ra-trustvox/sections/TrustvoxRateConfig.tsx": $$$$$$2, + "ra-trustvox/sections/TrustvoxStoreReviewsCarousel.tsx": $$$$$$3, + }, + "name": "ra-trustvox", + "baseUrl": import.meta.url, +}; + +export type Manifest = typeof manifest; + +export default manifest; diff --git a/ra-trustvox/mod.ts b/ra-trustvox/mod.ts new file mode 100644 index 000000000..c0a42d82b --- /dev/null +++ b/ra-trustvox/mod.ts @@ -0,0 +1,46 @@ +import { PreviewContainer } from "../utils/preview.tsx"; +import manifest, { Manifest } from "./manifest.gen.ts"; +import { type App, type AppContext as AC } from "@deco/deco"; +export interface State { + /** + * @title Store ID + * @description Store ID available on the Trustvox dashboard. + */ + storeId: string; + /** + * @title Number of reviews in store carousel. + * @description Number of reviews that should appear in the store carousel widget. + * @default 7 + */ + numberOfReviewsInStoreCarousel?: number; + /** + * @title Enable the staging environment. + * @description When enabling the testing environment, the store id must be replaced with a store id from a Trustvox testing environment store. + * @default false + */ + enableStaging?: boolean; +} +/** + * @title RA Trustvox + * @description RA trustvox reviews. + * @category Review + * @logo https://raw.githubusercontent.com/trustvox/deco-apps/enhancement/trustvox-app/ra-trustvox/ra-trustvox.png + */ +export default function RATrustvox(state: State): App { + return { manifest, state }; +} +export type AppContext = AC>; +export const preview = () => { + return { + Component: PreviewContainer, + props: { + name: "RA Trustvox", + owner: "deco.cx", + description: "RA trustvox reviews.", + logo: + "https://raw.githubusercontent.com/trustvox/deco-apps/enhancement/trustvox-app/ra-trustvox/ra-trustvox.png", + images: [], + tabs: [], + }, + }; +}; diff --git a/ra-trustvox/ra-trustvox.png b/ra-trustvox/ra-trustvox.png new file mode 100644 index 0000000000000000000000000000000000000000..16be38dcc9712e554c1601c9432645213d52e98a GIT binary patch literal 14426 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 literal 0 HcmV?d00001 diff --git a/ra-trustvox/sections/TrustvoxCertificate.tsx b/ra-trustvox/sections/TrustvoxCertificate.tsx new file mode 100644 index 000000000..3f6b24a62 --- /dev/null +++ b/ra-trustvox/sections/TrustvoxCertificate.tsx @@ -0,0 +1,15 @@ +import { AppContext } from "../mod.ts"; +import { type SectionProps } from "@deco/deco"; +export default function TrustvoxCertificate( + { enableStaging = false }: SectionProps, +) { + const scriptUrl = enableStaging + ? "https://storage.googleapis.com/trustvox-certificate-widget-staging/widget.js" + : "https://certificate.trustvox.com.br/widget.js"; + return + + ); +} + +export default Slider; diff --git a/utils/cookie.ts b/utils/cookie.ts new file mode 100644 index 000000000..6a37c72d9 --- /dev/null +++ b/utils/cookie.ts @@ -0,0 +1,39 @@ +import { getCookies, getSetCookies, setCookie } from "std/http/cookie.ts"; +import { DECO_SEGMENT, type Flag } from "@deco/deco"; +import { tryOrDefault } from "@deco/deco/utils"; +export const getFlagsFromRequest = (req: Request) => { + const cookies = getCookies(req.headers); + return getFlagsFromCookies(cookies); +}; +export const getFlagsFromCookies = (cookies: Record) => { + const flags: Flag[] = []; + const segment = cookies[DECO_SEGMENT] + ? tryOrDefault( + () => JSON.parse(decodeURIComponent(atob(cookies[DECO_SEGMENT]))), + {}, + ) + : {}; + segment.active?.forEach((flag: string) => + flags.push({ name: flag, value: true }) + ); + segment.inactiveDrawn?.forEach((flag: string) => + flags.push({ name: flag, value: false }) + ); + return flags; +}; +export const proxySetCookie = ( + from: Headers, + to: Headers, + toDomain?: URL | string, +) => { + const newDomain = toDomain && new URL(toDomain); + for (const cookie of getSetCookies(from)) { + const newCookie = newDomain + ? { + ...cookie, + domain: newDomain.hostname, + } + : cookie; + setCookie(to, newCookie); + } +}; diff --git a/utils/dataURI.ts b/utils/dataURI.ts new file mode 100644 index 000000000..f9760a8ff --- /dev/null +++ b/utils/dataURI.ts @@ -0,0 +1,31 @@ +let once = true; + +// Avoid throwing DOM Exception: +// The string to be encoded contains characters outside of the Latin1 range. +const btoaSafe = (x: string) => + btoa(`decodeURIComponent(escape(${unescape(encodeURIComponent(x))}))`); + +// deno-lint-ignore no-explicit-any +export const scriptAsDataURI = any>( + fn: T, + ...params: Parameters +) => { + if (once) { + once = false; + console.warn( + `scriptAsDataURI is deprecated and will soon be removed. Use import { useScriptAsDataURI } from 'deco/hooks/useScript.ts' instead.`, + ); + } + + return dataURI( + "text/javascript", + true, + `(${fn})(${params.map((p) => JSON.stringify(p)).join(", ")})`, + ); +}; + +export const dataURI = ( + contentType: "text/javascript", + base64: boolean, + content: string, +) => `data:${contentType}${base64 ? `;base64,${btoaSafe(content)}` : content}`; diff --git a/utils/defaultErrorPage.tsx b/utils/defaultErrorPage.tsx new file mode 100644 index 000000000..64c7498ad --- /dev/null +++ b/utils/defaultErrorPage.tsx @@ -0,0 +1,185 @@ +import { Head } from "$fresh/runtime.ts"; + +type Props = { + error?: string; +}; + +const Squares = () => ( + <> + + +
+
+
+
+
+ +
+

{name}

+

by {owner}

+
+
+

+ {description} +

+
+
+ {images.length > 0 && ( +
+
+ {images.length > 1 && ( +
+ + + +
+ )} + + {images.map((image, index) => { + return ( + + + + ); + })} + + {images.length > 1 && ( +
+ + + +
+ )} +
+ + +
+ )} +
+
+ {tabs?.map((tab, idx) => { + return ( +
+ + {tab.title} + +
+ {tab.content} +
+
+ ); + })} + {(tabs?.length || 0) > 0 && ( +
+
+ )} + +
+
+ +
+ ); +} + +const ArrowSvg = () => ( + + + +); diff --git a/utils/shortHash.ts b/utils/shortHash.ts new file mode 100644 index 000000000..172e4bc36 --- /dev/null +++ b/utils/shortHash.ts @@ -0,0 +1,21 @@ +export async function hashString(input: string): Promise { + const encoder = new TextEncoder(); + const data = encoder.encode(input); + + const hashBuffer = await crypto.subtle.digest("SHA-256", data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + + const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, "0")) + .join(""); + return hashHex.slice(0, 8); +} + +export function hashStringSync(input: string): string { + let hash = 0; + for (let i = 0; i < input.length; i++) { + const char = input.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash |= 0; // Convert to 32-bit integer + } + return hash.toString(16).slice(0, 8); +} diff --git a/utils/weakcache.ts b/utils/weakcache.ts new file mode 100644 index 000000000..59ba9e877 --- /dev/null +++ b/utils/weakcache.ts @@ -0,0 +1 @@ +export * as weakcache from "npm:weak-lru-cache@1.0.0"; diff --git a/verified-reviews/loaders/productDetailsPage.ts b/verified-reviews/loaders/productDetailsPage.ts new file mode 100644 index 000000000..917afa83a --- /dev/null +++ b/verified-reviews/loaders/productDetailsPage.ts @@ -0,0 +1,45 @@ +import { AppContext } from "../mod.ts"; +import { ProductDetailsPage } from "../../commerce/types.ts"; +import { ExtensionOf } from "../../website/loaders/extension.ts"; +import { + createClient, + getProductId, + PaginationOptions, +} from "../utils/client.ts"; +export type Props = PaginationOptions; + +/** + * @title Opiniões verificadas - Full Review for Product (Ratings and Reviews) + */ +export default function productDetailsPage( + config: Props, + _req: Request, + ctx: AppContext, +): ExtensionOf { + const client = createClient({ ...ctx }); + return async (productDetailsPage: ProductDetailsPage | null) => { + if (!productDetailsPage) { + return null; + } + + if (!client) { + return null; + } + + const productId = getProductId(productDetailsPage.product); + const fullReview = await client.fullReview({ + productId, + count: config?.count, + offset: config?.offset, + order: config?.order, + }); + + return { + ...productDetailsPage, + product: { + ...productDetailsPage.product, + ...fullReview, + }, + }; + }; +} diff --git a/verified-reviews/loaders/productList.ts b/verified-reviews/loaders/productList.ts new file mode 100644 index 000000000..b576646d8 --- /dev/null +++ b/verified-reviews/loaders/productList.ts @@ -0,0 +1,39 @@ +import { AppContext } from "../mod.ts"; +import { Product } from "../../commerce/types.ts"; +import { ExtensionOf } from "../../website/loaders/extension.ts"; +import { getRatingProduct } from "../utils/transform.ts"; +import { createClient, getProductId } from "../utils/client.ts"; + +/** + * @title Opiniões verificadas - Ratings for Products[] + */ +export default function productList( + _config: unknown, + _req: Request, + ctx: AppContext, +): ExtensionOf { + const client = createClient({ ...ctx }); + + return async (products: Product[] | null) => { + if (!products) { + return null; + } + if (!client) { + return products; + } + + const productsIds = products.map(getProductId); + const ratings = await client.ratings({ productsIds: productsIds }); + + return products.map((product) => { + const productId = getProductId(product); + return { + ...product, + aggregateRating: getRatingProduct({ + ratings, + productId: productId, + }), + }; + }); + }; +} diff --git a/verified-reviews/loaders/productListingPage.ts b/verified-reviews/loaders/productListingPage.ts new file mode 100644 index 000000000..f04d81670 --- /dev/null +++ b/verified-reviews/loaders/productListingPage.ts @@ -0,0 +1,47 @@ +import { AppContext } from "../mod.ts"; +import { ProductListingPage } from "../../commerce/types.ts"; +import { ExtensionOf } from "../../website/loaders/extension.ts"; +import { + createClient, + getProductId, + PaginationOptions, +} from "../utils/client.ts"; +import { getRatingProduct } from "../utils/transform.ts"; + +export type Props = PaginationOptions; + +/** + * @title Opiniões verificadas - Full Review for Product (Ratings and Reviews) + */ +export default function productListingPage( + _config: unknown, + _req: Request, + ctx: AppContext, +): ExtensionOf { + const client = createClient({ ...ctx }); + return async (page: ProductListingPage | null) => { + if (!page) { + return null; + } + if (!client) { + return page; + } + + const productsIds = page.products.map(getProductId); + const ratings = await client.ratings({ productsIds: productsIds }); + + return { + ...page, + products: page.products.map((product) => { + const productId = getProductId(product); + return { + ...product, + aggregateRating: getRatingProduct({ + ratings, + productId: productId, + }), + }; + }), + }; + }; +} diff --git a/verified-reviews/loaders/storeReview.ts b/verified-reviews/loaders/storeReview.ts new file mode 100644 index 000000000..c2e29534a --- /dev/null +++ b/verified-reviews/loaders/storeReview.ts @@ -0,0 +1,42 @@ +import { Review } from "../../commerce/types.ts"; +import { AppContext } from "../mod.ts"; +import { createClient } from "../utils/client.ts"; +import { toReview } from "../utils/transform.ts"; + +export type Props = { + /** + * @title Number of reviews + * @default 5 + */ + limit?: number; + /** + * @title Offset + * @default 0 + */ + offset?: number; +}; + +/** + * @title Opiniões verificadas - Full Review for Store (Ratings and Reviews) + */ +export default async function storeReview( + props: Props, + _req: Request, + ctx: AppContext, +): Promise { + const { offset = 0, limit = 5 } = props; + const client = createClient({ ...ctx }); + + if (!client) { + return null; + } + + const reviews = await client.storeReview(); + + if (!reviews) { + return null; + } + + // The API does not have a pagination, so we need to do it here + return reviews.map(toReview).slice(offset, limit); +} diff --git a/verified-reviews/logo.png b/verified-reviews/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4f19f17da538bf1237110730ffc1b9afabaebed3 GIT binary patch literal 21768 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 literal 0 HcmV?d00001 diff --git a/verified-reviews/manifest.gen.ts b/verified-reviews/manifest.gen.ts new file mode 100644 index 000000000..b6f337c9e --- /dev/null +++ b/verified-reviews/manifest.gen.ts @@ -0,0 +1,23 @@ +// DO NOT EDIT. This file is generated by deco. +// This file SHOULD be checked into source version control. +// This file is automatically updated during development when running `dev.ts`. + +import * as $$$0 from "./loaders/productDetailsPage.ts"; +import * as $$$1 from "./loaders/productList.ts"; +import * as $$$2 from "./loaders/productListingPage.ts"; +import * as $$$3 from "./loaders/storeReview.ts"; + +const manifest = { + "loaders": { + "verified-reviews/loaders/productDetailsPage.ts": $$$0, + "verified-reviews/loaders/productList.ts": $$$1, + "verified-reviews/loaders/productListingPage.ts": $$$2, + "verified-reviews/loaders/storeReview.ts": $$$3, + }, + "name": "verified-reviews", + "baseUrl": import.meta.url, +}; + +export type Manifest = typeof manifest; + +export default manifest; diff --git a/verified-reviews/mod.ts b/verified-reviews/mod.ts new file mode 100644 index 000000000..752b0fc78 --- /dev/null +++ b/verified-reviews/mod.ts @@ -0,0 +1,36 @@ +import type { Secret } from "../website/loaders/secret.ts"; +import manifest, { Manifest } from "./manifest.gen.ts"; +import { PreviewContainer } from "../utils/preview.tsx"; +import { type App, type AppContext as AC } from "@deco/deco"; +export interface ConfigVerifiedReviews { + idWebsite: string; + secretKey?: Secret; + plateforme?: string; +} +/** + * @title Verified Reviews + * @description A specialized solution in the collection of customer reviews + * @category Review + * @logo https://raw.githubusercontent.com/deco-cx/apps/main/verified-reviews/logo.png + */ +export default function App( + state: ConfigVerifiedReviews, +): App { + return { manifest, state }; +} +export type AppContext = AC>; +export const preview = () => { + return { + Component: PreviewContainer, + props: { + name: "Verified Reviews", + owner: "deco.cx", + description: + "A specialized solution in the collection of customer reviews.", + logo: + "https://raw.githubusercontent.com/deco-cx/apps/main/verified-reviews/logo.png", + images: [], + tabs: [], + }, + }; +}; diff --git a/verified-reviews/utils/client.ts b/verified-reviews/utils/client.ts new file mode 100644 index 000000000..4da18103a --- /dev/null +++ b/verified-reviews/utils/client.ts @@ -0,0 +1,186 @@ +import { fetchAPI } from "../../utils/fetch.ts"; +import { Ratings, Reviews, VerifiedReviewsFullReview } from "./types.ts"; +import { Product } from "../../commerce/types.ts"; +import { ConfigVerifiedReviews } from "../mod.ts"; +import { context } from "@deco/deco"; +export type ClientVerifiedReviews = ReturnType; +export interface PaginationOptions { + count?: number; + offset?: number; + order?: + | "date_desc" + | "date_ASC" + | "rate_DESC" + | "rate_ASC" + | "helpfulrating_DESC"; +} +const MessageError = { + ratings: + "🔴⭐ Error on call ratings of Verified Review - probably unidentified product", + rating: + "🔴⭐ Error on call single rating of Verified Review - probably unidentified product", + fullReview: + "🔴⭐ Error on call Full Review of Verified Review - probably unidentified product", +}; +const baseUrl = "https://awsapis3.netreviews.eu/product"; +export const createClient = (params: ConfigVerifiedReviews | undefined) => { + if (!params) { + return; + } + const { idWebsite } = params; + /** @description https://documenter.getpostman.com/view/2336519/SVzw6MK5#338f8f1b-4379-40a2-8893-080fe5234679 */ + const rating = async ({ productId }: { + productId: string; + }) => { + const payload = { + query: "average", + products: [productId], + idWebsite: idWebsite, + plateforme: "br", + }; + try { + const data = await fetchAPI(`${baseUrl}`, { + method: "POST", + body: JSON.stringify(payload), + }); + return Object.keys(data).length ? data : undefined; + } catch (error) { + if (context.isDeploy) { + console.error(MessageError.rating, error); + } else { + throw new Error(`${MessageError.rating} - ${error}`); + } + return undefined; + } + }; + /** @description https://documenter.getpostman.com/view/2336519/SVzw6MK5#6d8ab05a-28b6-48b3-9e8f-6bbbc046619a */ + const ratings = async ({ productsIds }: { + productsIds: string[]; + }) => { + const payload = { + query: "average", + products: productsIds, + idWebsite: idWebsite, + plateforme: "br", + }; + try { + const data = await fetchAPI(`${baseUrl}`, { + method: "POST", + body: JSON.stringify(payload), + }); + return Object.keys(data).length ? data : undefined; + } catch (error) { + if (context.isDeploy) { + console.error(MessageError.ratings, error); + } else { + console.log(`${MessageError.ratings} - ${error}`); + return undefined; + } + return undefined; + } + }; + /** @description https://documenter.getpostman.com/view/2336519/SVzw6MK5#daf51360-c79e-451a-b627-33bdd0ef66b8 */ + const reviews = ( + { productId, count = 5, offset = 0, order = "date_desc" }: + & PaginationOptions + & { + productId: string; + }, + ) => { + const payload = { + query: "reviews", + product: productId, + idWebsite: idWebsite, + plateforme: "br", + offset: offset, + limit: count, + order: order, + }; + return fetchAPI(`${baseUrl}`, { + method: "POST", + body: JSON.stringify(payload), + }); + }; + const fullReview = async ( + { productId, count = 5, offset = 0 }: PaginationOptions & { + productId: string; + }, + ): Promise => { + try { + const response = await Promise.all([ + rating({ productId }), + reviews({ productId, count, offset }), + ]); + const [responseRating, responseReview] = response.flat() as [ + Ratings, + Reviews | null, + ]; + const currentRating = responseRating?.[productId]?.[0]; + return { + aggregateRating: currentRating + ? { + "@type": "AggregateRating", + ratingValue: Number(parseFloat(currentRating.rate).toFixed(1)), + reviewCount: Number(currentRating.count), + } + : undefined, + review: responseReview + ? responseReview.reviews?.map((item) => ({ + "@type": "Review", + author: [ + { + "@type": "Author", + name: `${item.firstname} ${item.lastname}`, + }, + ], + datePublished: item.review_date, + reviewBody: item.review, + reviewRating: { + "@type": "AggregateRating", + ratingValue: Number(item.rate), + // this api does not support multiple reviews + reviewCount: 1, + }, + })) + : [], + }; + } catch (error) { + if (context.isDeploy) { + console.error(MessageError.ratings, error); + } else { + throw new Error(`${MessageError.fullReview} - ${error}`); + } + return { + aggregateRating: undefined, + review: [], + }; + } + }; + const storeReview = async (): Promise => { + try { + const response = await fetchAPI( + `https://cl.avis-verifies.com/br/cache/8/6/a/${idWebsite}/AWS/WEBSITE_API/reviews.json`, + { + method: "GET", + }, + ); + return (response ? response : []); + } catch (error) { + if (context.isDeploy) { + console.error(MessageError.ratings, error); + } else { + throw new Error(`${MessageError.ratings} - ${error}`); + } + return null; + } + }; + return { + rating, + ratings, + reviews, + fullReview, + storeReview, + }; +}; +export const getProductId = (product: Product) => + product.isVariantOf!.productGroupID; diff --git a/verified-reviews/utils/transform.ts b/verified-reviews/utils/transform.ts new file mode 100644 index 000000000..8b8c8fee8 --- /dev/null +++ b/verified-reviews/utils/transform.ts @@ -0,0 +1,40 @@ +import { + AggregateRating, + Review as CommerceReview, +} from "../../commerce/types.ts"; +import { Ratings, Review } from "./types.ts"; + +export const getRatingProduct = ({ + ratings, + productId, +}: { + ratings: Ratings | undefined; + productId: string; +}): AggregateRating | undefined => { + const rating = ratings?.[productId]?.[0]; + if (!rating) { + return undefined; + } + + return { + "@type": "AggregateRating", + ratingCount: Number(rating.count), + ratingValue: Number(parseFloat(rating.rate).toFixed(1)), + }; +}; + +export const toReview = (review: Review): CommerceReview => ({ + "@type": "Review", + author: [ + { + "@type": "Author", + name: `${review.firstname} ${review.lastname}`, + }, + ], + datePublished: review.review_date, + reviewBody: review.review, + reviewRating: { + "@type": "AggregateRating", + ratingValue: Number(review.rate), + }, +}); diff --git a/verified-reviews/utils/types.ts b/verified-reviews/utils/types.ts new file mode 100644 index 000000000..3a47c48ba --- /dev/null +++ b/verified-reviews/utils/types.ts @@ -0,0 +1,51 @@ +import { + AggregateRating, + Review as CommerceReview, +} from "../../commerce/types.ts"; + +export interface Ratings { + [key: string]: { + "rate": string; + "count": string; + }[]; +} + +export interface Review { + count_helpful_yes: string; + firstname: string; + order_ref: string; + rate: string; + review: string; + email: string; + count_helpful_no: string; + hide_personnal_data: string; + lastname: string; + medias: string; + order_date: string; + id_review_product: string; + review_date: string; + id_product: string; + id_review: string; + sign_helpful: string; + publish_date: string; + info1: string; + info2: string; + info3: string; + info4: string; + info5: string; + info6: string; + info7: string; + info8: string; + info9: string; + info10: string; +} + +export interface Reviews { + reviews: Review[]; + status: number[]; +} + +export interface VerifiedReviewsFullReview { + aggregateRating?: AggregateRating; + review: CommerceReview[]; +} diff --git a/vnda/README.md b/vnda/README.md new file mode 100644 index 000000000..9362fe685 --- /dev/null +++ b/vnda/README.md @@ -0,0 +1,11 @@ + + +Your online store with the best e-commerce platform. + +Loaders, actions and workflows for adding VNDA Commerce Platform to your deco.cx website. + +VNDA offers a range of features and services to facilitate e-commerce operations. + +This app wrapps VNDA Commerce API into a comprehensive set of +loaders/actions/workflows empowering non technical users to interact and act +upon their headless commerce. \ No newline at end of file diff --git a/vnda/actions/cart/addItem.ts b/vnda/actions/cart/addItem.ts index 761baa6a7..2b490ae5a 100644 --- a/vnda/actions/cart/addItem.ts +++ b/vnda/actions/cart/addItem.ts @@ -1,6 +1,7 @@ -import { setCookie } from "std/http/mod.ts"; +import { HttpError } from "../../../utils/http.ts"; +import cartLoader, { Cart } from "../../loaders/cart.ts"; import { AppContext } from "../../mod.ts"; -import type { Cart } from "../../utils/client/types.ts"; +import { getCartCookie } from "../../utils/cart.ts"; export interface Props { itemId: string; @@ -13,36 +14,23 @@ const action = async ( req: Request, ctx: AppContext, ): Promise => { - const { client } = ctx; + const { api } = ctx; const { itemId, quantity, attributes } = props; - const reqCookies = req.headers.get("cookie") ?? ""; + const cartId = getCartCookie(req.headers); - const { orderForm, cookies } = await client.carrinho.adicionar({ - cookie: reqCookies, - sku: itemId, - quantity, - attributes, - }); - - // in case the cart was created, set the cookie to the browser - for (const cookie of cookies) { - setCookie(ctx.response.headers, { - ...cookie, - domain: new URL(req.url).hostname, - }); + if (!cartId) { + throw new HttpError(400, "Missing cart cookie"); } - const allCookies = [ - reqCookies, - ...cookies.map(({ name, value }) => `${name}=${value}`), - ].join("; "); - - const relatedItems = await client.carrinho.relatedItems(allCookies); + await api["POST /api/v2/carts/:cartId/items"]({ cartId }, { + body: { + sku: itemId, + quantity, + extra: attributes, + }, + }); - return { - orderForm, - relatedItems, - }; + return cartLoader({}, req, ctx); }; export default action; diff --git a/vnda/actions/cart/addItems.ts b/vnda/actions/cart/addItems.ts new file mode 100644 index 000000000..7cd31f58b --- /dev/null +++ b/vnda/actions/cart/addItems.ts @@ -0,0 +1,46 @@ +import { HttpError } from "../../../utils/http.ts"; +import cartLoader, { Cart } from "../../loaders/cart.ts"; +import { AppContext } from "../../mod.ts"; +import { getCartCookie } from "../../utils/cart.ts"; + +interface Item { + itemId: string; + quantity: number; + attributes?: Record; +} + +export interface Props { + items: Item[]; +} + +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { api } = ctx; + const { items } = props; + const cartId = getCartCookie(req.headers); + + if (!cartId) { + throw new HttpError(400, "Missing cart cookie"); + } + + await api["POST /api/v2/carts/:cartId/items/bulk"]({ cartId }, { + body: { + items: items.map((item) => ({ + sku: item.itemId, + quantity: item.quantity, + customizations: item.attributes + ? Object.entries(item.attributes).map(([key, value]) => ({ + [key]: value, + })) + : undefined, + })), + }, + }); + + return cartLoader({}, req, ctx); +}; + +export default action; diff --git a/vnda/actions/cart/setShippingAddress.ts b/vnda/actions/cart/setShippingAddress.ts deleted file mode 100644 index 5bf2fce04..000000000 --- a/vnda/actions/cart/setShippingAddress.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { AppContext } from "../../mod.ts"; -import type { Cart } from "../../utils/client/types.ts"; - -export interface Props { - zip: string; -} - -const action = async ( - props: Props, - req: Request, - ctx: AppContext, -): Promise => { - const { client } = ctx; - const { zip } = props; - - const cookie = req.headers.get("cookie") ?? ""; - - const shipping = await client.cep(zip, cookie); - const updated = await ctx.invoke("apps/vnda/loaders/cart.ts"); - - return { shipping, ...updated }; -}; - -export default action; diff --git a/vnda/actions/cart/simulation.ts b/vnda/actions/cart/simulation.ts new file mode 100644 index 000000000..ff7c7ee67 --- /dev/null +++ b/vnda/actions/cart/simulation.ts @@ -0,0 +1,28 @@ +import { AppContext } from "../../mod.ts"; +import type { ShippingMethod } from "../../utils/client/types.ts"; +import { badRequest } from "@deco/deco"; +export interface Props { + skuId: string; + quantity: number; + zip: string; +} +const action = async ( + props: Props, + _req: Request, + ctx: AppContext, +): Promise => { + const { api } = ctx; + const { skuId, quantity, zip } = props; + if (!skuId || !quantity || !zip) { + badRequest({ + message: "could not find some props", + }); + } + const cep = await api["GET /api/v2/variants/:sku/shipping_methods"]({ + sku: skuId, + quantity, + zip, + }); + return cep.json(); +}; +export default action; diff --git a/vnda/actions/cart/updateCart.ts b/vnda/actions/cart/updateCart.ts new file mode 100644 index 000000000..39bb91f52 --- /dev/null +++ b/vnda/actions/cart/updateCart.ts @@ -0,0 +1,31 @@ +import { HttpError } from "../../../utils/http.ts"; +import cartLoader, { Cart } from "../../loaders/cart.ts"; +import { AppContext } from "../../mod.ts"; +import { getCartCookie } from "../../utils/cart.ts"; + +export interface Props { + agent?: string; + zip?: string; + client_id?: number; + coupon_code?: string; + rebate_token?: string; +} + +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { api } = ctx; + const cartId = getCartCookie(req.headers); + + if (!cartId) { + throw new HttpError(400, "Missing cart cookie"); + } + + await api["PATCH /api/v2/carts/:cartId"]({ cartId }, { body: props }); + + return cartLoader({}, req, ctx); +}; + +export default action; diff --git a/vnda/actions/cart/updateCoupon.ts b/vnda/actions/cart/updateCoupon.ts deleted file mode 100644 index f7d9a8fe4..000000000 --- a/vnda/actions/cart/updateCoupon.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { AppContext } from "../../mod.ts"; -import type { Cart } from "../../utils/client/types.ts"; - -export interface Props { - code: string; -} - -const action = async ( - props: Props, - req: Request, - ctx: AppContext, -): Promise => { - const { client } = ctx; - const { code } = props; - const cookie = req.headers.get("cookie") ?? ""; - - const coupon = await client.coupon(code, cookie); - const updated = await ctx.invoke("apps/vnda/loaders/cart.ts"); - - return { coupon, ...updated }; -}; - -export default action; diff --git a/vnda/actions/cart/updateItem.ts b/vnda/actions/cart/updateItem.ts index 76e0e37c6..3cda16269 100644 --- a/vnda/actions/cart/updateItem.ts +++ b/vnda/actions/cart/updateItem.ts @@ -1,5 +1,7 @@ +import { HttpError } from "../../../utils/http.ts"; +import cartLoader, { Cart } from "../../loaders/cart.ts"; import { AppContext } from "../../mod.ts"; -import type { Cart } from "../../utils/client/types.ts"; +import { getCartCookie } from "../../utils/cart.ts"; export interface Props { itemId: number | string; @@ -11,17 +13,23 @@ const action = async ( req: Request, ctx: AppContext, ): Promise => { - const { client } = ctx; - const { itemId: item_id, quantity } = props; - const cookie = req.headers.get("cookie") ?? ""; + const { api } = ctx; + const { itemId, quantity } = props; + const cartId = getCartCookie(req.headers); + + if (!cartId) { + throw new HttpError(400, "Missing cart cookie"); + } if (quantity > 0) { - await client.carrinho.atualizar({ item_id, quantity }, cookie); + await api["PATCH /api/v2/carts/:cartId/items/:itemId"]({ cartId, itemId }, { + body: { quantity }, + }); } else { - await client.carrinho.remover(item_id, cookie); + await api["DELETE /api/v2/carts/:cartId/items/:itemId"]({ cartId, itemId }); } - return ctx.invoke("apps/vnda/loaders/cart.ts"); + return cartLoader({}, req, ctx); }; export default action; diff --git a/vnda/actions/notifyme.ts b/vnda/actions/notifyme.ts new file mode 100644 index 000000000..166a0983d --- /dev/null +++ b/vnda/actions/notifyme.ts @@ -0,0 +1,43 @@ +import { AppContext } from "../mod.ts"; + +export interface Props { + key: string; + sku: string; + email: string; +} + +const action = async ( + props: Props, + _req: Request, + ctx: AppContext, +): Promise => { + const { account } = ctx; + const { key, sku, email } = props; + + const formdata = new FormData(); + formdata.append("key", key); + formdata.append("sku", sku); + formdata.append("email", email); + + const options = { + method: "POST", + body: formdata, + }; + + try { + await Promise.all([ + fetch( + `https://${account}.cdn.vnda.com.br/lista_de_espera`, + options, + ).then((res) => res.json()), + fetch( + `https://${account}.cdn.vnda.com.br/webform`, + options, + ), + ]); + } catch (error) { + console.log(error); + } +}; + +export default action; diff --git a/vnda/doccache.zst b/vnda/doccache.zst deleted file mode 100644 index 186ebc57e99ce5c758c5fe1e84f045db885e53c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47369 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 R!C96vPU_c(G}Xk%m>Bv)V*>yH diff --git a/vnda/handlers/sitemap.ts b/vnda/handlers/sitemap.ts new file mode 100644 index 000000000..fc560a988 --- /dev/null +++ b/vnda/handlers/sitemap.ts @@ -0,0 +1,73 @@ +import { AppContext } from "../mod.ts"; + +type ConnInfo = Deno.ServeHandlerInfo; +const xmlHeader = + ''; + +const includeSiteMaps = ( + currentXML: string, + origin: string, + includes?: string[], +) => { + const siteMapIncludeTags = []; + + for (const include of (includes ?? [])) { + siteMapIncludeTags.push(` + + ${include.startsWith("/") ? `${origin}${include}` : include} + ${new Date().toISOString().substring(0, 10)} +`); + } + return siteMapIncludeTags.length > 0 + ? currentXML.replace( + xmlHeader, + `${xmlHeader}\n${siteMapIncludeTags.join("\n")}`, + ) + : currentXML; +}; + +export interface Props { + include?: string[]; +} +/** + * @title Sitemap Proxy + */ +export default function Sitemap( + { include }: Props, + appCtx: AppContext, +) { + const { publicUrl } = appCtx; + return ( + req: Request, + _ctx: ConnInfo, + ) => { + if (!publicUrl) { + throw new Error("Missing publicUrl"); + } + + const url = new URL( + publicUrl?.startsWith("http") ? publicUrl : `https://${publicUrl}`, + ); + + const headers = new Headers(); + headers.set("Content-Type", "application/xml"); + + const reqUrl = new URL(req.url); + const text = `${xmlHeader} + + ${url.host}sitemap/vnda.xml + + `; + return new Response( + includeSiteMaps( + text.replaceAll(publicUrl, `${reqUrl.origin}/`), + reqUrl.origin, + include, + ), + { + headers, + status: 200, + }, + ); + }; +} diff --git a/vnda/hooks/context.ts b/vnda/hooks/context.ts index 162c8053d..6e0ac94d0 100644 --- a/vnda/hooks/context.ts +++ b/vnda/hooks/context.ts @@ -1,9 +1,9 @@ import { IS_BROWSER } from "$fresh/runtime.ts"; import { signal } from "@preact/signals"; -import { Runtime } from "../runtime.ts"; -import { Cart } from "../utils/client/types.ts"; +import { invoke } from "../runtime.ts"; +import type { Cart } from "../loaders/cart.ts"; -interface Context { +export interface Context { cart: Cart; } @@ -47,10 +47,8 @@ const enqueue = ( }; const load = (signal: AbortSignal) => - Runtime.invoke({ - cart: { - key: "apps/vnda/loaders/cart.ts", - }, + invoke({ + cart: invoke.vnda.loaders.cart(), }, { signal }); if (IS_BROWSER) { diff --git a/vnda/hooks/useCart.ts b/vnda/hooks/useCart.ts index eda74c1b2..fd30d4e14 100644 --- a/vnda/hooks/useCart.ts +++ b/vnda/hooks/useCart.ts @@ -1,48 +1,49 @@ -import { AnalyticsItem } from "apps/commerce/types.ts"; -import { Runtime } from "../runtime.ts"; -import { Cart, Item } from "../utils/client/types.ts"; -import { state as storeState } from "./context.ts"; +// deno-lint-ignore-file no-explicit-any +import type { AnalyticsItem } from "../../commerce/types.ts"; +import type { Manifest } from "../manifest.gen.ts"; +import { invoke } from "../runtime.ts"; +import { Context, state as storeState } from "./context.ts"; const { cart, loading } = storeState; +type Item = NonNullable["items"][number]; + export const itemToAnalyticsItem = ( item: Item & { quantity: number }, index: number, -): AnalyticsItem => ({ - item_id: `${item.id}_${item.variant_sku}`, - item_name: item.product_name, - discount: item.price - item.variant_price, - item_variant: item.variant_name.slice(item.product_name.length).trim(), - // TODO: check - price: item.price, - // TODO - // item_brand: "todo", - index, - quantity: item.quantity, -}); +): AnalyticsItem => { + return { + item_id: item.variant_sku, + item_group_id: item.id, + quantity: item.quantity, + price: item.price, + index, + discount: Math.abs(item.variant_price - item.price), + item_name: item.product_name, + item_variant: item.variant_name, + }; +}; + +type EnqueuableActions< + K extends keyof Manifest["actions"], +> = Manifest["actions"][K]["default"] extends + (...args: any[]) => Promise ? K : never; -const wrap = - (action: (p: T, init?: RequestInit | undefined) => Promise) => - (p: T) => - storeState.enqueue(async (signal) => ({ - cart: await action(p, { signal }), - })); +const enqueue = < + K extends keyof Manifest["actions"], +>(key: EnqueuableActions) => +(props: Parameters[0]) => + storeState.enqueue((signal) => + invoke({ cart: { key, props } } as any, { signal }) as any + ); const state = { cart, loading, - addItem: wrap( - Runtime.create("apps/vnda/actions/cart/addItem.ts"), - ), - updateItem: wrap( - Runtime.create("apps/vnda/actions/cart/updateItem.ts"), - ), - setShippingAddress: wrap( - Runtime.create("apps/vnda/actions/cart/setShippingAddress.ts"), - ), - updateCoupon: wrap( - Runtime.create("apps/vnda/actions/cart/updateCoupon.ts"), - ), + update: enqueue("vnda/actions/cart/updateCart.ts"), + addItem: enqueue("vnda/actions/cart/addItem.ts"), + updateItem: enqueue("vnda/actions/cart/updateItem.ts"), + simulate: invoke.vnda.actions.cart.simulation, }; export const useCart = () => state; diff --git a/vnda/loaders/cart.ts b/vnda/loaders/cart.ts index c19fbbb60..74bbb0680 100644 --- a/vnda/loaders/cart.ts +++ b/vnda/loaders/cart.ts @@ -1,5 +1,11 @@ import { AppContext } from "../mod.ts"; -import type { Cart } from "../utils/client/types.ts"; +import { getAgentCookie, getCartCookie, setCartCookie } from "../utils/cart.ts"; +import { OpenAPI } from "../utils/openapi/vnda.openapi.gen.ts"; + +export type Cart = { + orderForm?: OpenAPI["POST /api/v2/carts"]["response"]; + relatedItems?: OpenAPI["POST /api/v2/carts"]["response"]["items"]; +}; /** * @title VNDA Integration @@ -10,17 +16,41 @@ const loader = async ( req: Request, ctx: AppContext, ): Promise => { - const { client } = ctx; - const cookies = req.headers.get("cookie") ?? ""; + const { api } = ctx; + const cartId = getCartCookie(req.headers); + const agent = getAgentCookie(req.headers); + + let orderForm; + + try { + orderForm = cartId + ? await api["GET /api/v2/carts/:cartId"]({ cartId }).then((res) => + res.json() + ) + : await api["POST /api/v2/carts"]({}, { body: {} }).then((res) => + res.json() + ); + } catch (_error) { + // Failed to get current cardId, creating a new orderForm + orderForm = await api["POST /api/v2/carts"]({}, { body: {} }).then((res) => + res.json() + ); + } + + const hasAgent = orderForm.agent === agent; - const [orderForm, relatedItems] = await Promise.all([ - client.carrinho.get(cookies), - client.carrinho.relatedItems(cookies), - ]); + if (!hasAgent && agent) { + const [{ id }] = await api["GET /api/v2/users"]({ external_code: agent }) + .then((res) => res.json()); + await api["PATCH /api/v2/carts/:cartId"]({ cartId: orderForm.id }, { + body: { agent, user_id: id }, + }); + } + setCartCookie(ctx.response.headers, orderForm.id.toString()); return { orderForm, - relatedItems, + relatedItems: orderForm.items ?? [], }; }; diff --git a/vnda/loaders/extensions/price/list.ts b/vnda/loaders/extensions/price/list.ts new file mode 100644 index 000000000..c8904f980 --- /dev/null +++ b/vnda/loaders/extensions/price/list.ts @@ -0,0 +1,21 @@ +import { Product } from "../../../../commerce/types.ts"; +import { ExtensionOf } from "../../../../website/loaders/extension.ts"; +import { AppContext } from "../../../mod.ts"; +import { fetchAndApplyPrices } from "../../../utils/transform.ts"; + +export interface Props { + priceCurrency: string; +} + +const loader = ( + { priceCurrency }: Props, + req: Request, + ctx: AppContext, +): ExtensionOf => +(products: Product[] | null) => { + if (!Array.isArray(products)) return products; + + return fetchAndApplyPrices(products, priceCurrency, req, ctx); +}; + +export default loader; diff --git a/vnda/loaders/extensions/price/listingPage.ts b/vnda/loaders/extensions/price/listingPage.ts new file mode 100644 index 000000000..6131c01b7 --- /dev/null +++ b/vnda/loaders/extensions/price/listingPage.ts @@ -0,0 +1,35 @@ +import { ProductListingPage } from "../../../../commerce/types.ts"; +import { ExtensionOf } from "../../../../website/loaders/extension.ts"; +import { AppContext } from "../../../mod.ts"; +import { fetchAndApplyPrices } from "../../../utils/transform.ts"; + +export interface Props { + priceCurrency: string; +} + +const loader = ( + { priceCurrency }: Props, + req: Request, + ctx: AppContext, +): ExtensionOf => +async (props: ProductListingPage | null) => { + if (!props) return props; + + const { products, ...page } = props; + + if (!Array.isArray(products)) return props; + + const extendedProducts = await fetchAndApplyPrices( + products, + priceCurrency, + req, + ctx, + ); + + return { + ...page, + products: extendedProducts, + }; +}; + +export default loader; diff --git a/vnda/loaders/productDetailsPage.ts b/vnda/loaders/productDetailsPage.ts index cdd2dd6f7..ad959a032 100644 --- a/vnda/loaders/productDetailsPage.ts +++ b/vnda/loaders/productDetailsPage.ts @@ -1,10 +1,13 @@ import type { ProductDetailsPage } from "../../commerce/types.ts"; +import { STALE } from "../../utils/fetch.ts"; import type { RequestURLParam } from "../../website/functions/requestToParam.ts"; import { AppContext } from "../mod.ts"; -import { getSEOFromTag, parseSlug, toProduct } from "../utils/transform.ts"; +import { ProductPrice } from "../utils/client/types.ts"; +import { parseSlug, toProduct } from "../utils/transform.ts"; export interface Props { slug: RequestURLParam; + priceIntl?: boolean; } /** @@ -17,43 +20,109 @@ async function loader( ctx: AppContext, ): Promise { const url = new URL(req.url); - const { slug } = props; - const { client } = ctx; + const { slug, priceIntl = false } = props; + const { api } = ctx; if (!slug) return null; const variantId = url.searchParams.get("skuId") || null; - const { id } = parseSlug(slug); + const fromSlug = parseSlug(slug); - const [maybeProduct, seo] = await Promise.all([ - client.product.get(id), - client.seo.product(id), + // 404: invalid slug + if (!fromSlug) { + return null; + } + + const { id } = fromSlug; + + const getMaybeProduct = async (id: number) => { + try { + const result = await api["GET /api/v2/products/:id"]({ + id, + include_images: "true", + }, STALE); + return result.json(); + } catch (error) { + // Make async rendering work + if (error instanceof DOMException && error.name === "AbortError") { + throw error; + } + + return null; + } + }; + + // Since the Product by ID request don't return the INTL price, is necessary to search all prices and replace them + const getProductPrice = async (id: number): Promise => { + if (!priceIntl) { + return null; + } else { + try { + const result = await api["GET /api/v2/products/:productId/price"]({ + productId: id, + }, STALE); + return result.json(); + } catch (error) { + // Make async rendering work + if (error instanceof DOMException && error.name === "AbortError") { + throw error; + } + + return null; + } + } + }; + + const [maybeProduct, productPrice] = await Promise.all([ + getMaybeProduct(id), + getProductPrice(id), ]); + const variantsLength = maybeProduct?.variants?.length ?? 0; + // 404: product not found - if (!maybeProduct) { + if (!maybeProduct || variantsLength === 0) { return null; } const product = toProduct(maybeProduct, variantId, { url, priceCurrency: "BRL", + productPrice, }); + const segments = url.pathname.slice(1).split("/"); + + let seoArray; + if (product.isVariantOf?.productGroupID) { + seoArray = await api["GET /api/v2/seo_data"]({ + resource_type: "Product", + resource_id: Number(product.isVariantOf.productGroupID), + }, STALE).then((res) => res.json()) + .catch(() => undefined); + } + + const seo = seoArray?.at(-1); + return { "@type": "ProductDetailsPage", // TODO: Find out what's the right breadcrumb on vnda breadcrumbList: { "@type": "BreadcrumbList", - itemListElement: [], - numberOfItems: 0, + itemListElement: segments.map((s, i) => ({ + "@type": "ListItem", + name: s, + position: i + 1, + item: new URL(`/${segments.slice(0, i + 1).join("/")}`, url).href, + })), + numberOfItems: segments.length, }, product, - seo: getSEOFromTag({ - title: product.name, - description: product.description || "", - ...seo?.[0], - }, req), + seo: { + title: seo?.title || (product.name ?? ""), + description: seo?.description || (product.description ?? ""), + canonical: new URL(`/${segments.join("/")}`, url).href, + }, }; } diff --git a/vnda/loaders/productDetailsPageVideo.ts b/vnda/loaders/productDetailsPageVideo.ts new file mode 100644 index 000000000..53fbb948c --- /dev/null +++ b/vnda/loaders/productDetailsPageVideo.ts @@ -0,0 +1,28 @@ +import { AppContext } from "../mod.ts"; +import { ExtensionOf } from "../../website/loaders/extension.ts"; +import { ProductDetailsPage } from "../../commerce/types.ts"; +import { addVideoToProduct } from "../utils/transform.ts"; +import { STALE } from "../../utils/fetch.ts"; + +export default function productDetailsPageVideo( + _props: unknown, + _req: Request, + ctx: AppContext, +): ExtensionOf { + const { api } = ctx; + return async (productDetailsPage: ProductDetailsPage | null) => { + if (!productDetailsPage) { + return null; + } + const { product } = productDetailsPage; + const { inProductGroupWithID } = product; + const videos = await api["GET /api/v2/products/:productId/videos"]({ + productId: inProductGroupWithID as string, + }, STALE).then((r) => r.json()).catch(() => null); + const productWithVideo = addVideoToProduct(product, videos); + return { + ...productDetailsPage, + product: productWithVideo, + }; + }; +} diff --git a/vnda/loaders/productList.ts b/vnda/loaders/productList.ts index 4acc3a49e..4ca265206 100644 --- a/vnda/loaders/productList.ts +++ b/vnda/loaders/productList.ts @@ -1,4 +1,5 @@ -import type { Product } from "apps/commerce/types.ts"; +import type { Product } from "../../commerce/types.ts"; +import { STALE } from "../../utils/fetch.ts"; import type { AppContext } from "../mod.ts"; import { toProduct } from "../utils/transform.ts"; @@ -17,6 +18,12 @@ export interface Props { /** @description search for products that have certain tag */ tags?: string[]; + + /** @description search for products that have certain type_tag */ + typeTags?: { key: string; value: string }[]; + + /** @description search for products by id */ + ids: number[]; } /** @@ -29,22 +36,39 @@ const productListLoader = async ( ctx: AppContext, ): Promise => { const url = new URL(req.url); - const { client } = ctx; - - const search = await client.product.search({ - term: props?.term, - wildcard: props?.wildcard, - sort: props?.sort, - per_page: props?.count, - tags: props?.tags, + const { api } = ctx; + + const { results: searchResults = [] } = await api + ["GET /api/v2/products/search"]({ + term: props?.term, + wildcard: props?.wildcard, + sort: props?.sort, + per_page: props?.count, + "tags[]": props?.tags, + ...Object.fromEntries( + (props.typeTags || []).map(( + { key, value }, + ) => [`type_tags[${key}][]`, value]), + ), + "ids[]": props?.ids, + }, STALE).then((res) => res.json()); + + const validProducts = searchResults.filter(({ variants }) => { + return variants.length !== 0; }); - return search.results.map((product) => - toProduct(product, null, { + if (validProducts.length === 0) return null; + + const sortedProducts = props.ids?.length > 0 + ? props.ids.map((id) => validProducts.find((product) => product.id === id)) + : validProducts; + + return sortedProducts.map((product) => { + return toProduct(product!, null, { url, priceCurrency: "BRL", - }) - ); + }); + }); }; export default productListLoader; diff --git a/vnda/loaders/productListingPage.ts b/vnda/loaders/productListingPage.ts index b030bf9ed..874da1ab6 100644 --- a/vnda/loaders/productListingPage.ts +++ b/vnda/loaders/productListingPage.ts @@ -1,14 +1,20 @@ -import { Sort } from "../utils/client/types.ts"; +import type { + BreadcrumbList, + ProductListingPage, +} from "../../commerce/types.ts"; import { SortOption } from "../../commerce/types.ts"; +import { STALE } from "../../utils/fetch.ts"; +import type { RequestURLParam } from "../../website/functions/requestToParam.ts"; +import type { AppContext } from "../mod.ts"; +import { ProductSearchResult, Sort } from "../utils/client/types.ts"; +import { Tag } from "../utils/openapi/vnda.openapi.gen.ts"; import { + canonicalFromTags, getSEOFromTag, toFilters, toProduct, typeTagExtractor, } from "../utils/transform.ts"; -import type { ProductListingPage } from "../../commerce/types.ts"; -import type { RequestURLParam } from "../../website/functions/requestToParam.ts"; -import type { AppContext } from "../mod.ts"; export const VNDA_SORT_OPTIONS: SortOption[] = [ { value: "", label: "Relevância" }, @@ -18,6 +24,15 @@ export const VNDA_SORT_OPTIONS: SortOption[] = [ { value: "highest_price", label: "Maior preço" }, ]; +type Operators = "and" | "or"; + +interface FilterOperator { + type_tags?: Operators; + property1?: Operators; + property2?: Operators; + property3?: Operators; +} + export interface Props { /** * @description overides the query term @@ -37,8 +52,38 @@ export interface Props { * Slug for category pages */ slug?: RequestURLParam; + + filterByTags?: boolean; + + /** @description if properties are empty, "typeTags" value will apply to all. Defaults to "and" */ + filterOperator?: FilterOperator; + + /** + * @hide true + * @description The URL of the page, used to override URL from request + */ + pageHref?: string; } +const getBreadcrumbList = (categories: Tag[], url: URL): BreadcrumbList => ({ + "@type": "BreadcrumbList" as const, + itemListElement: categories.map((t, index) => ({ + "@type": "ListItem" as const, + item: canonicalFromTags(categories.slice(0, index + 1), url).href, + position: index + 1, + name: t.title, + })), + numberOfItems: categories.length, +}); + +const handleOperator = ( + key: "type_tags" | "property1" | "property2" | "property3", + defaultValue: Operators, + filterOperators?: FilterOperator, +) => ({ + [`${key}_operator`]: filterOperators?.[key] ?? defaultValue ?? "and", +}); + /** * @title VNDA Integration * @description Product Listing Page loader @@ -49,76 +94,168 @@ const searchLoader = async ( ctx: AppContext, ): Promise => { // get url from params - const url = new URL(req.url); - const { client } = ctx; + const url = new URL(props.pageHref || req.url); + const { api } = ctx; const count = props.count ?? 12; - const { cleanUrl, typeTags } = typeTagExtractor(url); const sort = url.searchParams.get("sort") as Sort; const page = Number(url.searchParams.get("page")) || 1; - const isSearchPage = url.pathname === "/busca"; + const isSearchPage = ctx.searchPagePath + ? ctx.searchPagePath === url.pathname + : url.pathname === "/busca" || url.pathname === "/s"; const qQueryString = url.searchParams.get("q"); const term = props.term || props.slug || qQueryString || undefined; - const search = await client.product.search({ - term, - sort, - page, - per_page: count, - tags: props.tags, - type_tags: typeTags, - wildcard: true, - }); + const priceFilterRegex = /de-(\d+)-a-(\d+)/; + const filterMatch = url.href.match(priceFilterRegex) ?? []; + + const categoryTagName = (props.term || url.pathname.slice(1) || "").split( + "/", + ); + + const properties1 = url.searchParams.getAll("type_tags[property1][]"); + const properties2 = url.searchParams.getAll("type_tags[property2][]"); + const properties3 = url.searchParams.getAll("type_tags[property3][]"); + + const categoryTagNames = Array.from(url.searchParams.values()); + + const tags = await Promise.all([ + ...categoryTagNames, + ...categoryTagName.filter((item): item is string => + typeof item === "string" + ), + ].map((name) => + api["GET /api/v2/tags/:name"]({ name }, STALE) + .then((res) => res.json()) + .catch(() => undefined) + )); - const categoryTagName = props.term || url.pathname.split("/").pop() || ""; - const [seo, categoryTag] = await Promise.all([ - client.seo.tag(categoryTagName), - isSearchPage - ? client.tag(categoryTagName).catch(() => undefined) - : undefined, + const categories = tags + .slice(-categoryTagName.length) + .filter((tag): tag is Tag => + typeof tag !== "undefined" && typeof tag.name !== "undefined" + ); + + const filteredTags = tags + .filter((tag): tag is Tag => typeof tag !== "undefined"); + + const { cleanUrl, typeTags } = typeTagExtractor(url, filteredTags); + + const initialTags = props.tags && props.tags?.length > 0 + ? props.tags + : undefined; + + const categoryTagsToFilter = categories.length > 0 && props.filterByTags + ? categories.map((t) => t.name) + .filter((name): name is string => typeof name === "string") + : undefined; + + const defaultOperator = props.filterOperator?.type_tags ?? "and"; + + const preference = categoryTagsToFilter + ? term + : qQueryString ?? url.pathname.slice(1); + + const tag = categories.at(-1); + + const [response, seo = []] = await Promise.all([ + await api["GET /api/v2/products/search"]({ + term: term ?? preference, + sort, + page, + per_page: count, + "tags[]": initialTags ?? categoryTagsToFilter, + wildcard: true, + ...(filterMatch[1] && { min_price: Number(filterMatch[1]) }), + ...(filterMatch[2] && { max_price: Number(filterMatch[2]) }), + "property1_values[]": properties1, + "property2_values[]": properties2, + "property3_values[]": properties3, + ...handleOperator("type_tags", defaultOperator, props.filterOperator), + ...handleOperator("property1", defaultOperator, props.filterOperator), + ...handleOperator("property2", defaultOperator, props.filterOperator), + ...handleOperator("property3", defaultOperator, props.filterOperator), + ...Object.fromEntries( + typeTags.reduce]>>( + (acc, { key, value, isProperty }) => { + if (isProperty) return acc; + + const pos = acc.findIndex((item) => item[0] === key); + + if (pos !== -1) { + acc[pos] = [key, [...acc[pos][1], value]]; + return acc; + } + + return [...acc, [key, [value]]]; + }, + [], + ), + ), + }, STALE), + api["GET /api/v2/seo_data"]( + { resource_type: "Tag", code: tag?.name }, + STALE, + ).then((res) => res.json()) + .catch(() => undefined), ]); - const { results: searchResults, pagination } = search; - const products = searchResults.map((product) => - toProduct(product, null, { + const pagination = JSON.parse( + response.headers.get("x-pagination") ?? "null", + ) as ProductSearchResult["pagination"] | null; + + const search = await response.json(); + + const { results: searchResults = [] } = search; + + const validProducts = searchResults.filter(({ variants }) => { + return variants.length !== 0; + }); + + const products = validProducts.map((product) => { + return toProduct(product, null, { url, priceCurrency: "BRL", - }) - ); + }); + }); const nextPage = new URLSearchParams(url.searchParams); const previousPage = new URLSearchParams(url.searchParams); - if (pagination.next_page) { + if (pagination?.next_page) { nextPage.set("page", (page + 1).toString()); } - if (pagination.prev_page) { + if (pagination?.prev_page) { previousPage.set("page", (page - 1).toString()); } - const hasSEO = !isSearchPage && (seo?.[0] || categoryTag); + const hasTypeTags = !![ + ...typeTags, + ...properties1, + ...properties2, + ...properties3, + ].length; return { "@type": "ProductListingPage", - seo: hasSEO - ? getSEOFromTag({ ...categoryTag, ...seo?.[0] }, req) - : undefined, - // TODO: Find out what's the right breadcrumb on vnda - breadcrumb: { - "@type": "BreadcrumbList", - itemListElement: [], - numberOfItems: 0, - }, + seo: getSEOFromTag(categories, url, seo.at(-1), hasTypeTags, isSearchPage), + breadcrumb: isSearchPage + ? { + "@type": "BreadcrumbList", + itemListElement: [], + numberOfItems: 0, + } + : getBreadcrumbList(categories, url), filters: toFilters(search.aggregations, typeTags, cleanUrl), - products: products, + products, pageInfo: { - nextPage: pagination.next_page ? `?${nextPage}` : undefined, - previousPage: pagination.prev_page ? `?${previousPage}` : undefined, + nextPage: pagination?.next_page ? `?${nextPage}` : undefined, + previousPage: pagination?.prev_page ? `?${previousPage}` : undefined, currentPage: page, - records: pagination.total_count, + records: pagination?.total_count, recordPerPage: count, }, sortOptions: VNDA_SORT_OPTIONS, diff --git a/vnda/loaders/proxy.ts b/vnda/loaders/proxy.ts new file mode 100644 index 000000000..20e6a0606 --- /dev/null +++ b/vnda/loaders/proxy.ts @@ -0,0 +1,133 @@ +import { Route } from "../../website/flags/audience.ts"; +import { AppContext } from "../mod.ts"; + +const PAGE_PATHS = [ + "/admin", + "/admin/*", + "/carrinho", + "/carrinho/*", + "/cdn-cgi/*", + "/cep", + "/cep/*", + "/checkout/*", + "/common/*", + "/components/*", + "/conta", + "/conta/*", + "/cupom/ajax", + "/entrar", + "/entrar/*", + "/images/*", + "/javascripts/*", + "/loja/configuracoes", + "/pagamento/*", + "/pedido/*", + "/recaptcha", + "/recuperar_senha", + "/sair", + "/sitemap/vnda.xml", + "/stylesheets/*", + "/v/s", + "/webform", +]; + +const API_PATHS = [ + "/api/*", +]; + +const decoSiteMapUrl = "/sitemap/deco.xml"; + +const VNDA_HOST_HEADER = "X-Shop-Host"; +export interface Props { + /** @description ex: /p/fale-conosco */ + pagesToProxy?: string[]; + generateDecoSiteMap?: boolean; + /** + * @title Exclude paths from /deco-sitemap.xml + */ + excludePathsFromDecoSiteMap?: string[]; + includeSiteMap?: string[]; +} + +/** + * @title VNDA Proxy Routes + */ +function loader( + { + pagesToProxy = [], + generateDecoSiteMap, + excludePathsFromDecoSiteMap = [], + includeSiteMap, + }: Props, + _req: Request, + { publicUrl, account }: AppContext, +): Route[] { + const internalDomain = `https://${account}.cdn.vnda.com.br/`; + const url = new URL( + publicUrl?.startsWith("http") ? publicUrl : `https://${publicUrl}`, + ); + + const customHeaders = [{ key: VNDA_HOST_HEADER, value: url.hostname }]; + + const [include, routes] = generateDecoSiteMap + ? [ + [...(includeSiteMap ?? []), decoSiteMapUrl], + [{ + pathTemplate: decoSiteMapUrl, + handler: { + value: { + excludePaths: excludePathsFromDecoSiteMap, + __resolveType: "website/handlers/sitemap.ts", + }, + }, + }], + ] + : [includeSiteMap, []]; + + const internalDomainPaths = [ + ...PAGE_PATHS, + ...pagesToProxy, + ].map(( + pathTemplate, + ) => ({ + pathTemplate, + handler: { + value: { + __resolveType: "website/handlers/proxy.ts", + avoidAppendPath: pathTemplate === "/sitemap/vnda.xml", + url: pathTemplate === "/sitemap/vnda.xml" + ? `https://sitemap.vnda.com.br/preview/${publicUrl}` + : internalDomain, + host: url.hostname, + customHeaders, + }, + }, + })); + + const siteMap = { + pathTemplate: "/sitemap.xml", + handler: { + value: { + include, + __resolveType: "vnda/handlers/sitemap.ts", + customHeaders, + }, + }, + }; + + const apiDomainPaths = API_PATHS.map((pathTemplate) => ({ + pathTemplate, + handler: { + value: { + __resolveType: "website/handlers/proxy.ts", + url: `https://api.vnda.com.br/`, + host: url.hostname, + customHeaders, + }, + }, + })); + + return [...routes, ...internalDomainPaths, siteMap, ...apiDomainPaths]; +} + +export default loader; diff --git a/vnda/logo.png b/vnda/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f78f44c573e2c378e1cbb2d518f250b506314bf6 GIT binary patch literal 17387 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 { + const segment = getSegmentFromBag(ctx); + if (!segment) { + const segmentFromRequest = buildSegmentCookie(req); + const segmentFromCookie = getSegmentFromCookie(req); + if ( + segmentFromRequest !== null && + !equal(segmentFromRequest, segmentFromCookie) + ) { + setSegmentInBag(ctx, segmentFromRequest); + setSegmentCookie(segmentFromRequest, ctx.response.headers); + } + } + return ctx.next!(); +}; diff --git a/vnda/mod.ts b/vnda/mod.ts index 1100ee3a1..ad4285a1a 100644 --- a/vnda/mod.ts +++ b/vnda/mod.ts @@ -1,9 +1,18 @@ -import type { App, FnContext } from "$live/mod.ts"; -import manifest, { Manifest, name } from "./manifest.gen.ts"; -import { createClient } from "./utils/client/client.ts"; - +import { Markdown } from "../decohub/components/Markdown.tsx"; +import { createHttpClient } from "../utils/http.ts"; +import { PreviewContainer } from "../utils/preview.tsx"; +import type { Secret } from "../website/loaders/secret.ts"; +import manifest, { Manifest } from "./manifest.gen.ts"; +import { middleware } from "./middleware.ts"; +import { OpenAPI } from "./utils/openapi/vnda.openapi.gen.ts"; +import { + type App, + type AppMiddlewareContext as AMC, + type FnContext, +} from "@deco/deco"; +export type AppMiddlewareContext = AMC>; export type AppContext = FnContext; - +/** @title VNDA */ export interface Props { /** * @title VNDA Account name @@ -11,40 +20,82 @@ export interface Props { * @default deco */ account: string; - /** * @title Public store URL * @description Domain that is registered on VNDA * @default www.mystore.com.br */ publicUrl: string; - /** * @description The token generated from admin panel. Read here: https://developers.vnda.com.br/docs/chave-de-acesso-e-requisicoes. Do not add any other permissions than catalog. */ - authToken: string; - + authToken: Secret; /** * @title Use Sandbox * @description Define if sandbox environment should be used */ sandbox: boolean; + /** + * @description Use VNDA as backend platform + * @hide true + */ + platform: "vnda"; + /** @description Here is to put the pathname of the Search Page. Ex: /s. We have default values: "/busca" or "/s" */ + searchPagePath?: string; } - export interface State extends Props { - client: ReturnType; + api: ReturnType>; } - +export const color = 0x0C29D0; /** * @title VNDA + * @description Loaders, actions and workflows for adding VNDA Commerce Platform to your website. + * @category Ecommmerce + * @logo https://raw.githubusercontent.com/deco-cx/apps/main/vnda/logo.png */ -export default function App(props: Props): App { +export default function VNDA(props: Props): App { + const { authToken, publicUrl, sandbox } = props; + const stringAuthToken = typeof authToken === "string" + ? authToken + : authToken?.get?.() ?? ""; + const api = createHttpClient({ + headers: new Headers({ + "User-Agent": "decocx/1.0", + "X-Shop-Host": publicUrl, + "accept": "application/json", + authorization: `Bearer ${stringAuthToken}`, + }), + base: sandbox + ? "https://api.sandbox.vnda.com.br" + : "https://api.vnda.com.br", + }); return { - name, - state: { - ...props, - client: createClient(props), - }, + state: { ...props, api }, manifest, + middleware, }; } +export const preview = async () => { + const markdownContent = await Markdown( + new URL("./README.md", import.meta.url).href, + ); + return { + Component: PreviewContainer, + props: { + name: "VNDA", + owner: "deco.cx", + description: + "Loaders, actions and workflows for adding VNDA Commerce Platform to your website.", + logo: "https://raw.githubusercontent.com/deco-cx/apps/main/vnda/logo.png", + images: [ + "https://deco-sites-assets.s3.sa-east-1.amazonaws.com/starting/8deab172-eca8-45dd-85f9-c44f66b1cfb1/Hub_de_Integracao_Tiny_91206b57b3_94dac840e3.webp", + ], + tabs: [ + { + title: "About", + content: markdownContent(), + }, + ], + }, + }; +}; diff --git a/vnda/runtime.ts b/vnda/runtime.ts index c6d780f6f..da42a2435 100644 --- a/vnda/runtime.ts +++ b/vnda/runtime.ts @@ -1,4 +1,3 @@ -import { forApp } from "$live/clients/withManifest.ts"; -import app from "./mod.ts"; - -export const Runtime = forApp>(); +import { Manifest } from "./manifest.gen.ts"; +import { proxy } from "@deco/deco/web"; +export const invoke = proxy(); diff --git a/vnda/utils/cart.ts b/vnda/utils/cart.ts new file mode 100644 index 000000000..3364e552d --- /dev/null +++ b/vnda/utils/cart.ts @@ -0,0 +1,29 @@ +import { getCookies, setCookie } from "std/http/cookie.ts"; +import { SEGMENT_COOKIE_NAME } from "./segment.ts"; + +const CART_COOKIE = "vnda_cart_id"; + +const ONE_WEEK_MS = 7 * 24 * 3600 * 1_000; + +export const getCartCookie = (headers: Headers): string | undefined => { + const cookies = getCookies(headers); + + return cookies[CART_COOKIE]; +}; + +export const getAgentCookie = (headers: Headers): string | undefined => { + const cookies = getCookies(headers); + + return cookies[SEGMENT_COOKIE_NAME]; +}; + +export const setCartCookie = (headers: Headers, cartId: string) => + setCookie(headers, { + name: CART_COOKIE, + value: cartId, + path: "/", + expires: new Date(Date.now() + ONE_WEEK_MS), + httpOnly: true, + secure: true, + sameSite: "Lax", + }); diff --git a/vnda/utils/client/client.ts b/vnda/utils/client/client.ts index 67a3a0206..85e8c2853 100644 --- a/vnda/utils/client/client.ts +++ b/vnda/utils/client/client.ts @@ -1,235 +1,129 @@ -import { getSetCookies } from "std/http/cookie.ts"; -import { FetchOptions, fetchSafe } from "../../../utils/fetch.ts"; -import { HttpError } from "../../../utils/HttpError.ts"; -import { Props } from "../../mod.ts"; -import { paramsToQueryString } from "../queryBuilder.ts"; import { - Banner, - Coupon, + Item, OrderForm, ProductGroup, - ProductSearchParams, + ProductPrice, ProductSearchResult, - RelatedItem, RelatedItemTag, SEO, - Shipping, + ShippingMethod, + Sort, TagsSearchParams, } from "./types.ts"; -export const createClient = (state: Props) => { - const { publicUrl, sandbox, authToken } = state; - const publicEndpoint = `https://${publicUrl}`; // TODO: Remove this and use only api endpoints - const baseUrl = sandbox - ? "https://api.sandbox.vnda.com.br" - : "https://api.vnda.com.br"; - - const fetcher = (path: string, init?: RequestInit & FetchOptions) => - fetchSafe(new URL(path, baseUrl), { - ...init, - headers: { - "User-Agent": "decocx/1.0", - "X-Shop-Host": publicUrl, - "accept": "application/json", - authorization: `Bearer ${authToken}`, - ...init?.headers, - }, - }); - - const getProduct = (id: string | number): Promise => - fetcher(`/api/v2/products/${id}`, { withProxyCache: true }) - .then((res) => res.json()) - .catch(() => null); - - const searchProduct = async ( - params: ProductSearchParams, - ): Promise => { - const { type_tags, ...knownParams } = params; - const typeTagsEntries = type_tags?.map((tag) => [tag.key, tag.value]) ?? []; - - const qs = paramsToQueryString({ - ...knownParams, - ...Object.fromEntries(typeTagsEntries), - }); - - const response = await fetcher(`/api/v2/products/search?${qs}`, { - withProxyCache: true, - }); +export interface API { + /** @docs https://developers.vnda.com.br/reference/get-api-v2-products-id */ + "GET /api/v2/products/:id": { + response: ProductGroup; + searchParams: { include_images: boolean }; + }; - const data = await response.json(); - const pagination = response.headers.get("x-pagination"); + /** @docs https://developers.vnda.com.br/reference/get-api-v2-products-product_id-price */ + "GET /api/v2/products/:productId/price": { + response: ProductPrice; + searchParams: { coupon_codes?: string[] }; + }; - return { - ...data, - pagination: pagination ? JSON.parse(pagination) : { - total_pages: 0, - total_count: 0, - current_page: params.page, - prev_page: false, - next_page: false, - }, + /** @docs https://developers.vnda.com.br/reference/get-api-v2-banners */ + "GET /api/v2/banners": { + searchParams: { + only_valid: boolean; + tag: "listagem-banner-principal"; }; }; - const getDefaultBanner = (): Promise => - fetcher( - `/api/v2/banners?only_valid=true&tag=listagem-banner-principal`, - { withProxyCache: true }, - ).then((res) => res.json()); - - const getSEO = (type: "Product" | "Page" | "Tag") => - ( - resourceId: string | number, - ): Promise => { - const qs = new URLSearchParams(); - qs.set("resource_type", type); - if (type !== "Tag") qs.set("resource_id", `${resourceId}`); - if (type === "Tag") qs.set(`code`, `${resourceId}`); - qs.set("type", "category"); - - return fetcher(`/api/v2/seo_data?${qs.toString()}`, { - withProxyCache: true, - }).then((res) => res.json()); + /** @docs https://developers.vnda.com.br/reference/get-api-v2-tags-name */ + "GET /api/v2/tags/:name": { + response: RelatedItemTag; }; - const getProductSEO = getSEO("Product"); - const getPageSEO = getSEO("Page"); - const getTagSEO = getSEO("Tag"); - - const getTag = (name: string): Promise => - fetcher(`/api/v2/tags/${name}`, { withProxyCache: true }) - .then((res) => res.json()); - - const getTags = (params?: TagsSearchParams): Promise => { - const qs = new URLSearchParams(); - Object.entries(params ?? {}).forEach(([key, value]) => { - qs.set(key, value); - }); - - return fetcher(`/api/v2/tags?${qs.toString()}`, { withProxyCache: true }) - .then((res) => res.json()); + /** @docs https://developers.vnda.com.br/reference/get-api-v2-tags */ + "GET /api/v2/tags": { + response: RelatedItemTag[]; + searchParams: TagsSearchParams; }; - const getCarrinho = (cookie: string): Promise => - fetcher(new URL("/carrinho", publicEndpoint).href, { - headers: { cookie }, - }).then((res) => res.json()); - - const relatedItems = (cookie: string): Promise => - fetcher( - new URL( - "/carrinho/produtos-sugeridos/relacionados-carrinho", - publicEndpoint, - ).href, - { headers: { cookie } }, - ).then((res) => res.json()).catch((error) => { - if (error instanceof HttpError && error.status === 404) { - return []; - } - - throw error; - }); + "GET /api/v2/seo_data": { + response: SEO[]; + searchParams: { + resource_type: "Product" | "Page"; + resource_id: string | number; + type: "category"; + } | { + resource_type: "Tag"; + code: string; + type: "category"; + }; + }; - const adicionar = async ({ - cookie, - sku, - quantity, - attributes, - }: { - cookie: string; - sku: string; - quantity: number; - attributes: Record; - }) => { - const form = new FormData(); - form.set("sku", sku); - form.set("quantity", `${quantity}`); + /** @docs https://developers.vnda.com.br/reference/get-api-v2-products-search */ + "GET /api/v2/products/search": { + response: Omit; + searchParams: { + term?: string | undefined; + page?: number; + "tags[]"?: string[]; + sort?: Sort; + per_page?: number; + wildcard?: boolean; + type_tags_operator?: string; + } & { [x: string]: unknown | unknown[] }; + }; - Object.entries(attributes).forEach(([name, value]) => - form.set(`attribute-${name}`, value) - ); + /** @docs https://developers.vnda.com.br/reference/get-api-v2-carts-id */ + "GET /api/v2/carts/:cartId": { + response: OrderForm; + }; - const response = await fetcher( - new URL("/carrinho/adicionar", publicEndpoint).href, - { - method: "POST", - body: form, - headers: { cookie }, - }, - ); + /** @docs https://developers.vnda.com.br/reference/post-api-v2-carts */ + "POST /api/v2/carts": { + response: OrderForm; + }; - return { - orderForm: await response.json() as OrderForm, - cookies: getSetCookies(response.headers), + /** @docs https://developers.vnda.com.br/reference/get-api-v2-carts-id */ + "PATCH /api/v2/carts/:cartId": { + response: OrderForm; + body: { + agent?: string; + zip?: string; + client_id?: number; + coupon_code?: string; + rebate_token?: string; }; }; - const cep = (zip: string, cookie: string): Promise => { - const form = new FormData(); - form.set("zip", zip); - - return fetcher(new URL("/cep", publicEndpoint).href, { - method: "POST", - body: form, - headers: { cookie }, - }) - .then((res) => res.json()); + /** @docs https://developers.vnda.com.br/reference/post-api-v2-carts-cart_id-items */ + "POST /api/v2/carts/:cartId/items": { + response: Item; + body: { + sku: string; + quantity: number; + place_id?: number; + store_coupon_code?: string; + customizations?: Record; + extra?: Record; + }; }; - const coupon = (code: string, cookie: string): Promise => { - const form = new FormData(); - form.set("code", code); - - return fetcher(new URL("/cupom/ajax", publicEndpoint).href, { - method: "POST", - body: form, - headers: { cookie }, - }).then((res) => res.json()); + /** @docs https://developers.vnda.com.br/reference/patch-api-v2-carts-cart_id-items-id */ + "PATCH /api/v2/carts/:cartId/items/:itemId": { + response: Item; + body: { + sku?: string; + quantity: number; + place_id?: number; + store_coupon_code?: string; + customizations?: Record; + extra?: Record; + }; }; - const atualizar = ( - { item_id, quantity }: { item_id: string | number; quantity: number }, - cookie: string, - ): Promise => - fetcher(new URL("/carrinho/quantidade/atualizar", publicEndpoint).href, { - method: "POST", - body: JSON.stringify({ item_id, quantity }), - headers: { cookie }, - }).then((res) => res.json()); - - const remover = ( - item_id: string | number, - cookie: string, - ): Promise => - fetcher(new URL("/carrinho/remover", publicEndpoint).href, { - method: "POST", - body: JSON.stringify({ item_id }), - headers: { cookie }, - }).then((res) => res.json()); + /** @docs https://developers.vnda.com.br/reference/delete-api-v2-carts-cart_id-items-id */ + "DELETE /api/v2/carts/:cartId/items/:itemId": undefined; - return { - product: { - search: searchProduct, - get: getProduct, - }, - banners: { - default: getDefaultBanner, - }, - seo: { - product: getProductSEO, - page: getPageSEO, - tag: getTagSEO, - }, - tag: getTag, - tags: getTags, - carrinho: { - get: getCarrinho, - relatedItems, - adicionar, - atualizar, - remover, - }, - cep, - coupon, + /** @docs https://developers.vnda.com.br/reference/get-api-v2-variants-variant_sku-shipping_methods */ + "GET /api/v2/variants/:sku/shipping_methods": { + response: ShippingMethod[]; + searchParams: { quantity: number; zip: string }; }; -}; +} diff --git a/vnda/utils/client/types.ts b/vnda/utils/client/types.ts index ca2a6cb64..5accc3d1a 100644 --- a/vnda/utils/client/types.ts +++ b/vnda/utils/client/types.ts @@ -1,3 +1,5 @@ +import { ProductPriceVariant } from "../openapi/vnda.openapi.gen.ts"; + export type Sort = "newest" | "oldest" | "lowest_price" | "highest_price"; export interface ProductSearchResult { @@ -50,6 +52,12 @@ export type ProductGroup = Partial<{ }; attributes: Record; tags: RelatedItemTag[]; + images?: { + id: number; + updated_at: string; + url: string; + variant_ids: unknown[]; + }[]; }>; export interface Attribute { @@ -106,8 +114,6 @@ export interface Property { export interface Cart { orderForm?: OrderForm; relatedItems?: RelatedItem[]; - shipping?: Shipping; - coupon?: Coupon; } export interface Installment { @@ -148,7 +154,7 @@ export interface OrderForm { channel: string; client_id: null; code: string; - coupon_code: null; + coupon_code: string | null; discount: null; discount_price: number; extra: Record; @@ -209,11 +215,15 @@ export interface ShippingMethod { description: string; name: string; price: number; - shipping_method_id: number; + shipping_method_id: number | null; value: string; - countries: string[] | null; + countries: { + country?: string; + price?: string; + }[] | null; fulfillment_company: string | null; value_needed_to_discount: number | null; + notice: string | null; } export interface Address { @@ -370,9 +380,22 @@ export interface Banner { export interface SEO { id: number; + name?: string; title?: string; description?: string | null; resource_type: string; resource_id: number; parent_id: null | number; } + +export interface ProductPrice { + available: boolean; + on_sale: boolean; + price: number; + sale_price: number; + intl_price: number; + discount_rule?: unknown; + updated_at: string; + installments: Installment[]; + variants: ProductPriceVariant[]; +} diff --git a/vnda/utils/constants.ts b/vnda/utils/constants.ts new file mode 100644 index 000000000..e69de29bb diff --git a/vnda/utils/openapi/vnda.openapi.gen.ts b/vnda/utils/openapi/vnda.openapi.gen.ts new file mode 100644 index 000000000..6bdf30999 --- /dev/null +++ b/vnda/utils/openapi/vnda.openapi.gen.ts @@ -0,0 +1,5412 @@ + +// deno-fmt-ignore-file +// deno-lint-ignore-file no-explicit-any ban-types ban-unused-ignore +// +// DO NOT EDIT. This file is generated by deco. +// This file SHOULD be checked into source version control. +// To generate this file: deno task start +// + + +export interface OpenAPI { +"GET /api/v2/products/:productId/videos": { +response: { +id?: number +url?: string +embed_url?: string +thumbnail_url?: string +updated_at?: number +variant_ids?: number +}[] +} +"GET /api/v2/seo_data": { +searchParams: { +resource_type?: string +resource_id?: number +type?: string +code?: string +} +response: { +id: number +title?: string +description?: string +resource_type: string +resource_id: number +parent_id: number +}[] +} +/** + * Permite autorizar operações usando o access_token e a senha do usuário + */ +"POST /api/v2/users/authorize": { +body: { +access_token: string +password: string +} +} +/** + * Realiza o login do usuário a partir do email e da senha + */ +"POST /api/v2/users/login": { +body: { +email: string +password: string +} +response: User +} +/** + * Realiza o logout do usuário a partir do access_token do mesmo + */ +"POST /api/v2/users/logout": { +body: { +/** + * Token de validação de usuário logado + * + * O `access_token` é gerado quando o usuário loga no Admin + */ +access_token?: string +} +} +/** + * Retorna os dados de um usuário pelo seu ID + */ +"GET /api/v2/users/:id": { +response: User +} +/** + * Atualiza um usuário + */ +"PUT /api/v2/users/:id": { +body: { +email?: string +name?: string +role_name?: ("Agente" | "Gestor" | "Local") +password?: string +password_confirmation?: string +external_code?: string +phone_area?: string +phone?: string +tags?: string[] +} +} +/** + * Retorna as versões da regra de bônus cadastrada + */ +"GET /api/v2/credits/rules/versions": { +response: { +event?: string +author?: string +created_at?: string +ip?: string +user_agent?: string +cart_id?: string +object_changes?: string +} +} +/** + * Retorna as regras de bônus cadastradas + */ +"GET /api/v2/credits/rules": { +response: { +active: boolean +minimum_subtotal: number +bonus: number +delayed_for: number +valid_for: number +maximum_usage_factor: number +} +} +/** + * Permite atualizar as regras de bônus + */ +"PUT /api/v2/credits/rules": { +body: { +/** + * Percentual em cima do total do pedido que vai ser dado de bônus para o cliente + */ +bonus: number +/** + * Número de dias em que o crédito começa a valer + */ +valid_in: number +/** + * Número de dias para a expiração do crédito + */ +valid_for: number +/** + * Valor mínimo do pedido para que o bônus possa ser transferido para o cliente + */ +minimum_subtotal?: number +/** + * Percentual do subtotal do pedido que pode ser pago com o bônus + */ +maximum_usage_factor?: number +} +} +/** + * Permite remover as regras de bônus, desativando o recurso + */ +"DELETE /api/v2/credits/rules": { + +} +/** + * Retorna os dados de um pedido usando o `code` ou `token` + */ +"GET /api/v2/orders/:code": { +searchParams: { +/** + * Retorna as formas de entrega do pedido + */ +include_shipping_address?: boolean +} +response: Order +} +/** + * Retorna uma lista de pedidos + */ +"GET /api/v2/orders": { +searchParams: { +/** + * Retorna os resultados a partir desta data, no formato 'yyyy-mm-dd' + */ +start?: string +/** + * Retorna os resultados até esta data, no formato 'yyyy-mm-dd' + */ +finish?: string +/** + * Se "true" retorna somente os pedidos que tenham nota fiscal. Se "false" retorna somente os pedidos que não tenham nota fiscal + */ +invoiced?: boolean +/** + * Número da página atual. Os dados de paginação estarão disponíveis, em formato JSON, no header X-Pagination no response da API, caso exista paginação + */ +page?: number +/** + * Número máximo de registros que deve ser retornado por página + */ +per_page?: number +/** + * Array com os códigos de cupons + */ +coupon_codes?: string[] +/** + * Se "true" inclui o preço dos produtos customizados no total do pedido. Se "false" retorna o total do pedido sem a somatória do preço de produtos customizados. + */ +include_customizations_in_total?: boolean +} +response: Order[] +} +/** + * Faz a captura do pagamento no adquirente + * Apenas para pedidos pagos com cartão de crédito + */ +"POST /api/v2/orders/:code/capture": { +response: { + +} +} +/** + * Altera o status do pedido para "confirmado" + */ +"POST /api/v2/orders/:code/confirm": { +body: { +/** + * Para cartão de crédito deve ser enviado OBRIGATORIAMENTE o retorno da requisição para "/api/v2/orders/{code}/capture" + */ +confirmation_data?: string +} +} +/** + * Faz o estorno do pagamento no adquirente + * Apenas para pedidos pagos com cartão de crédito + */ +"POST /api/v2/orders/:code/chargeback": { + +} +/** + * Altera o status do pedido para "cancelado" + */ +"POST /api/v2/orders/:code/cancel": { +body: { +/** + * Deve ser enviado algo que comprove que o pagamento foi devolvido. + * Para cartão de crédito deve ser enviado OBRIGATORIAMENTE o retorno da requisição para "/api/v2/orders/{code}/chargeback" + */ +cancelation_data?: string +} +response: { + +} +} +/** + * Altera o status do pacote para "enviado" + */ +"PATCH /api/v2/orders/:orderCode/packages/:packageCode/ship": { + +} +/** + * Altera o pacote para "entregue" + */ +"PATCH /api/v2/orders/:orderCode/packages/:packageCode/deliver": { + +} +/** + * Recebe uma lista JSON com os SKUs que devem ser atualizados. A atualização será executada em segundo plano em aproximadamente 1 minuto + */ +"POST /api/v2/variants/quantity": { +body: { +sku: string +quantity: number +/** + * Informe somente para atualizar o estoque de um local específico + */ +place_id?: number +}[] +} +/** + * Atualiza o estoque de uma variante de um produto + */ +"POST /api/v2/variants/:sku/quantity": { +searchParams: { +quantity: number +} +response: { +status?: string +} +} +/** + * Atualiza o estoque específico de um local + */ +"PATCH /api/v2/variants/:sku/inventories/:placeId": { +searchParams: { +quantity?: number +} +response: { +status?: string +} +} +/** + * Permite listar as variantes de um produto + */ +"GET /api/v2/products/:productId/variants": { +response: Variant[] +} +/** + * Permite criar uma variante + */ +"POST /api/v2/products/:productId/variants": { +body: { +sku: string +name?: string +quantity: number +main?: boolean +/** + * Largura do produto, em centímetros + */ +width?: number +/** + * Altura do produto, em centímetros + */ +height?: number +/** + * Comprimento do produito, em centímetros + */ +length?: number +/** + * Massa do produto, em gramas + */ +weight?: number +/** + * Dias de manuseio da variante + */ +handling_days?: number +price: number +/** + * Customização da variante + */ +custom_attributes?: { + +} +min_quantity?: number +norder?: number +property1?: string +property2?: string +property3?: string +barcode?: string +} +response: { +id?: number +main?: boolean +available?: boolean +sku?: string +name?: string +slug?: string +min_quantity?: number +quantity?: number +/** + * Quantidade de itens disponíveis + */ +stock?: number +/** + * Customização da variante + */ +custom_attributes?: { + +} +properties?: { + +} +/** + * Data e horário da última atualização + */ +updated_at?: string +price?: number +installments?: number[] +available_quantity?: number +/** + * Massa do produto, em gramas + */ +weight?: number +/** + * Largura do produto, em centímetros + */ +width?: number +/** + * Altura do produto, em centímetros + */ +height?: number +/** + * Comprimento do produito, em centímetros + */ +length?: number +/** + * Dias de manuseio da variante + */ +handling_days?: number +inventories?: VariantInventory[] +sale_price?: number +image_url?: string +product_id?: number +norder?: number +} +} +/** + * Permite remover uma variante + */ +"DELETE /api/v2/products/:productId/variants/:id": { + +} +/** + * @deprecated + * Permite atualizar uma variante + */ +"PATCH /api/v2/products/:productId/variants/:id": { +body: { +sku: string +name?: string +quantity: number +main?: boolean +width?: number +height?: number +length?: number +weight?: number +handling_days?: number +price: number +custom_attributes?: { + +} +min_quantity?: number +norder?: number +property1?: string +property2?: string +property3?: string +barcode?: string +quantity_sold?: number +} +} +/** + * Permite determinar a ordem das variantes dentro de cada produto + */ +"POST /api/v2/variants/reorder": { +body: { +/** + * A ordem dos elementos será replicada para as variantes + */ +ids: number[] +} +} +/** + * Retorna um template usando o path dele + */ +"GET /api/v2/templates/:path": { +response: Template +} +/** + * Remove um template usando o path dele + */ +"DELETE /api/v2/templates/:path": { + +} +/** + * Atualiza o conteúdo de um template usando o path dele + */ +"PATCH /api/v2/templates/:path": { +body: { +body?: string +} +} +/** + * Retorna uma lista de templates + */ +"GET /api/v2/templates": { +response: Template[] +} +/** + * Cria um novo template + */ +"POST /api/v2/templates": { +body: { +path: string +body?: string +} +response: Template +} +/** + * Reativa um usuário que estiver desativado + */ +"POST /api/v2/users/:id/activate": { + +} +/** + * Desativa um usuário + */ +"POST /api/v2/users/:id/deactivate": { + +} +/** + * Lista os usuários + */ +"GET /api/v2/users": { +searchParams: { +/** + * Incluir usuários desativados? + */ +include_inactive?: boolean +/** + * Incluir todas as imagens dos produtos? + */ +include_images?: boolean +/** + * Exibe somente os usuários com o código externo indicado + */ +external_code?: string +/** + * Exibe somente os usuários com a função indicada + */ +role_name?: ("Agente" | "Gestor" | "Local") +/** + * Filtra usuários que contenham o valor indicado no nome, telefone, email ou código externo + */ +term?: string +} +response: User1[] +} +/** + * Cria um usuário + */ +"POST /api/v2/users": { +body: { +email?: string +name?: string +role_name?: ("Agente" | "Gestor" | "Local") +password?: string +password_confirmation?: string +external_code?: string +phone_area?: string +phone?: string +tags?: string[] +} +response: User +} +/** + * Lista os usuários + */ +"GET /api/v2/users/tags": { +searchParams: { +/** + * Exibe somente os usuários com a função indicada + */ +role_names?: ("Agente" | "Gestor" | "Local" | "Agente Social Selling") +} +response: { +tags?: string[] +} +} +/** + * Retorna a lista de carrinhos ativos nos últimos 60 dias + */ +"GET /api/v2/carts": { +searchParams: { +/** + * Número da página + */ +page?: number +/** + * Quantidade de produtos por página + */ +per_page?: number +/** + * Inclui os carrinhos sem telefone (não enviar o campo para não incluir) + */ +without_phones?: boolean +/** + * Filtra os carrinhos que possuem tentativa de pagamento + */ +with_payments?: boolean +} +response: Cart[] +} +/** + * Permite criar um carrinho + */ +"POST /api/v2/carts": { +body: ParametrosDeCarrinhoResumido +response: Cart1 +} +/** + * Permite retornar um carrinho + */ +"GET /api/v2/carts/:id": { +response: Cart1 +} +/** + * Permite criar um carrinho + */ +"POST /api/v2/carts/:id": { +body: { +agent?: string +zip?: string +client_id?: number +coupon_code?: string +/** + * DEPRECATED: enviar o `client_id` + */ +email?: string +rebate_token?: string +} +response: Cart1 +} +/** + * Permite excluir um carrinho + */ +"DELETE /api/v2/carts/:id": { + +} +/** + * Permite atualizar os atributos de um carrinho + */ +"PATCH /api/v2/carts/:id": { +body: { +agent?: string +zip?: string +client_id?: number +/** + * DEPRECATED: enviar o `client_id` + */ +email?: string +rebate_token?: string +} +} +/** + * Permite calcular as parcelas referentes ao total do carrinho + */ +"GET /api/v2/carts/:id/installments": { +response: CartInstallment1[] +} +/** + * Lista os locais + */ +"GET /api/v2/places": { +searchParams: { +/** + * Filtra os locais for nome + */ +names?: string[] +/** + * Filtra os locais que são/não são warehouse + */ +warehouse?: boolean +/** + * Filtra os locais que contenham determinada categoria + */ +category?: string +/** + * As lojas mais próximas da coordenada informada serão exibidas primeiro + */ +coordinates?: string +/** + * As lojas mais próximas do CEP informado serão exibidas primeiro + */ +origin_zip_code?: string +} +response: Place[] +} +/** + * Cria um local + */ +"POST /api/v2/places": { +body: { +name: string +address_line_1: string +address_line_2?: string +city: string +neighborhood?: string +zip?: string +home_page?: string +latitude?: number +longitude?: number +images?: string[] +description?: string +email: string +first_phone?: string +second_phone?: string +mobile_phone?: string +only_cash?: boolean +categories?: string[] +marker_url?: string +state?: string +opening_hours?: string +warehouse?: boolean +legal_name?: string +cnpj?: string +} +response: Place +} +/** + * Remove um local + */ +"DELETE /api/v2/places/:id": { + +} +/** + * Atualiza um local + */ +"PATCH /api/v2/places/:id": { +body: { +name: string +address_line_1: string +address_line_2?: string +city: string +neighborhood?: string +zip?: string +home_page?: string +latitude?: number +longitude?: number +images?: string[] +description?: string +email: string +first_phone?: string +second_phone?: string +mobile_phone?: string +only_cash?: boolean +categories?: string[] +marker_url?: string +state?: string +opening_hours?: string +warehouse?: boolean +legal_name?: string +cnpj?: string +} +} +/** + * Lista as notas fiscais + */ +"GET /api/v2/orders/:code/packages/:packageCode/invoices": { +response: Invoice[] +} +/** + * Cria uma nota fiscal + */ +"POST /api/v2/orders/:code/packages/:packageCode/invoices": { +body: { +number: number +series?: number +issued_at?: string +key?: string +volumes?: number +} +response: Invoice +} +/** + * Remove uma nota fiscal + */ +"DELETE /api/v2/orders/:code/packages/:packageCode/invoices/:number": { + +} +/** + * Atualiza uma nota fiscal + */ +"PATCH /api/v2/orders/:code/packages/:packageCode/invoices/:number": { +body: { +number: number +series?: number +issued_at?: string +key?: string +volumes?: number +} +} +/** + * Será enviado por email um link para o cadastro da nova senha + * O link tem validade de 24 horas + */ +"POST /api/v2/users/reset_password": { +body: { +email: string +} +} +/** + * Cadastra a nova senha + */ +"PATCH /api/v2/users/reset_password": { +body: { +/** + * Token pare renovação de senha enviado por email + */ +token: string +/** + * Nova senha para o usuário + */ +password: string +/** + * Confirmação da nova senha do usuário + */ +password_confirmation: string +} +} +/** + * Retorna o endereço de entrega + */ +"GET /api/v2/orders/:code/shipping_address": { +response: { +id?: number +first_name: string +last_name: string +company_name?: string +email: string +/** + * Serão retornados apenas os campos preenchidos + */ +documents?: { +cpf?: string +cnpj?: string +ie?: string +} +street_name: string +street_number: string +complement?: string +neighborhood: string +/** + * Somente números + */ +first_phone_area: string +/** + * Somente números + */ +first_phone: string +/** + * Somente números + */ +second_phone_area?: string +/** + * Somente números + */ +second_phone?: string +reference?: string +/** + * Somente números + */ +zip: string +city: string +state: string +recipient_name?: string +} +} +/** + * Lista os recebedores + */ +"GET /api/v2/payment_recipients": { +response: PaymentRecipient[] +} +/** + * Cria um recebedor + */ +"POST /api/v2/payment_recipients": { +body: { +tag_id?: number +recipient_id?: number +percentage: number +active?: boolean +charge_processing_fee?: boolean +liable?: boolean +code?: string +place_id?: number +user_id?: number +/** + * Indica se o frete deve ser incluído no split do pagamento + */ +include_shipping?: boolean +} +response: PaymentRecipient +} +/** + * Retorna um recebedor + */ +"GET /api/v2/payment_recipients/:id": { +response: PaymentRecipient +} +/** + * Remove um recebedor + */ +"DELETE /api/v2/payment_recipients/:id": { + +} +/** + * Atualiza um recebedor + */ +"PATCH /api/v2/payment_recipients/:id": { +body: { +tag_id?: number +recipient_id?: number +percentage?: number +active?: boolean +charge_processing_fee?: boolean +liable?: boolean +code?: string +place_id?: number +user_id?: number +/** + * Indica se o frete deve ser incluído no split do pagamento + */ +include_shipping?: boolean +} +} +/** + * Permite a listagem de recebíveis do usuário + */ +"GET /api/v2/users/:userId/payables": { +response: RecebiveisDoUsuario[] +} +/** + * Lista os membros da audiência + */ +"GET /api/v2/audience_members": { +response: AudienceMember[] +} +/** + * Permite criar um membro da audiência + */ +"POST /api/v2/audience_members": { +body: { +first_name?: (null | string) +last_name?: (null | string) +email: string +phone_area?: (null | string) +phone?: (null | string) +tags?: string[] +} +response: AudienceMember +} +/** + * Permite remover um membro da audiência + */ +"DELETE /api/v2/audience_members/:id": { + +} +/** + * Permite alterar um membro da audiência + */ +"PATCH /api/v2/audience_members/:id": { +body: { +first_name?: string +last_name?: string +email?: string +phone_area?: string +phone?: string +tags?: string[] +} +} +/** + * Lista os rastreios de um pacote de um pedido + */ +"GET /api/v2/orders/:orderCode/packages/:packageCode/trackings": { +response: { +id?: number +/** + * Código de rastreio do pacote + */ +tracking_code: string +/** + * Data e horário da última atualização do código de rastreio do pacote + */ +tracked_at?: string +/** + * URL para rastreio do pedido com a transportadora + */ +url?: string +/** + * Transportadora do pacote + */ +company?: string +} +} +/** + * Adiciona um rastreio para um pacote de um pedido + */ +"POST /api/v2/orders/:orderCode/packages/:packageCode/trackings": { +body: { +/** + * Código de rastreio + */ +code: string +/** + * Transportadora + */ +company?: string +/** + * Link de rastreamento + */ +url?: string +} +response: { +/** + * Código de rastreio do pacote + */ +code: string +/** + * Transportadora + */ +company?: string +/** + * URL para rastreio do pacote na transportadora + */ +url?: string +} +} +/** + * Remove um rastreio + */ +"DELETE /api/v2/orders/:orderCode/packages/:packageCode/trackings/:id": { + +} +/** + * Lista os itens de um carrinho + */ +"GET /api/v2/carts/:cartId/items": { +response: CartItem[] +} +/** + * Permite criar um item do carrinho + */ +"POST /api/v2/carts/:cartId/items": { +body: Produto +response: CartItem +} +/** + * Remove um item do carrinho + */ +"DELETE /api/v2/carts/:cartId/items/:id": { + +} +/** + * Atualiza um item do carrinho + */ +"PATCH /api/v2/carts/:cartId/items/:id": { +body: { +quantity?: number +place_id?: number +extra?: { + +} +store_coupon_code?: string +} +} +/** + * Permite adicionar itens em bulk ao carrinho + */ +"POST /api/v2/carts/:cartId/items/bulk": { +body: { +/** + * Itens do carrinho + */ +items?: { +/** + * Código SKU da variante do produto + */ +sku: string +/** + * Unidades do produto + */ +quantity: number +/** + * [Personalização](http://ajuda.vnda.com.br/pt-BR/articles/1763398-funcionalidades-produtos-personalizados) do produto + */ +customizations?: { +/** + * Adicione a customização de acordo com a [personalização](http://ajuda.vnda.com.br/pt-BR/articles/1763398-funcionalidades-produtos-personalizados) incluídas no Admin da loja. + * Se por exemplo a customização do produto é a cor, o parâmetro para a requisição deve ser `Color` ao invés de `CUstomization`. + * Saiba mais sobre como utilizar esse parâmetro pelo exemplo de requsição localizado na seção de **Request Example** (ao lado do código da requisição). + */ +Customization?: string +}[] +}[] +minItems?: 0 +} +response: CartItem[] +} +/** + * Cria uma promoção + */ +"POST /api/v2/discounts": { +body: { +name: string +start_at: string +end_at?: string +valid_to?: ("store" | "cart") +description?: string +enabled?: boolean +email?: string +cpf?: string +tags?: string +} +response: Discount1 +} +/** + * Retorna uma promoção + */ +"GET /api/v2/discounts/:id": { +response: Discount1 +} +/** + * Remove uma promoção + */ +"DELETE /api/v2/discounts/:id": { + +} +/** + * Altera uma promoção + */ +"PATCH /api/v2/discounts/:id": { +body: { +name: string +start_at: string +end_at?: string +valid_to?: ("store" | "cart") +description?: string +enabled?: boolean +email?: string +cpf?: string +tags?: string +} +} +/** + * Lista as regras de desconto de uma promoção + */ +"GET /api/v2/discounts/:discountId/rules": { +response: DiscountRule[] +} +/** + * Cria uma regra de desconto + */ +"POST /api/v2/discounts/:discountId/rules": { +body: { +apply_to?: ("product" | "tag" | "subtotal" | "total" | "shipping") +amount_type?: ("R$" | "%") +amount?: number +product_id?: number +tag_name?: string +min_quantity?: number +shipping_method?: string +min_subtotal?: number +gift?: boolean +combinated_product_id?: number +client_tag?: string +shipping_rule?: string +gift_quantity?: number +agent_tag?: string +regions?: string[] +channel?: string[] +} +response: { +id?: number +amount?: number +apply_to?: ("product" | "tag" | "subtotal" | "total" | "shipping") +min_quantity?: number +type?: string +channel?: string[] +} +} +/** + * Remove uma regra de desconto + */ +"DELETE /api/v2/discounts/:discountId/rules/:id": { + +} +/** + * Altera uma regra de desconto + */ +"PATCH /api/v2/discounts/:discountId/rules/:id": { +body: { +apply_to?: ("product" | "tag" | "subtotal" | "total" | "shipping") +amount_type?: ("R$" | "%") +amount?: number +product_id?: number +tag_id?: number +min_quantity?: number +shipping_method?: string +min_subtotal?: number +gift?: boolean +combinated_product_id?: number +client_tag?: string +shipping_rule?: string +gift_quantity?: number +agent_tag?: string +regions?: string[] +channel?: string[] +} +} +/** + * Permite listar os cupons de desconto de uma promoção + */ +"GET /api/v2/discounts/:discountId/coupons": { +searchParams: { +/** + * Filtra os cupons pelo campo uses_per_code + */ +uses_per_code?: number +} +response: Coupon[] +} +/** + * Cria um cupom de desconto + */ +"POST /api/v2/discounts/:discountId/coupons": { +body: { +code?: string +uses_per_code?: number +uses_per_user?: number +referrer_email?: string +quantity?: number +user_id?: number +} +response: Coupon +} +/** + * Remove um cupom de desconto + */ +"DELETE /api/v2/discounts/:discountId/coupons:id": { + +} +/** + * Atualiza um cupom de desconto + */ +"PATCH /api/v2/discounts/:discountId/coupons:id": { +body: { +/** + * Caso deseje um uso ilimitado do cupom, o valor desse campo deverá ser 0 + */ +uses_per_code?: number +/** + * Caso deseje um uso ilimitado do cupom, o valor desse campo deverá ser 0 + */ +uses_per_user?: number +} +} +/** + * Lista os produtos + */ +"GET /api/v2/products": { +searchParams: { +/** + * Delimita a quantidade de itens retornados + */ +limit?: number +/** + * Número da página + */ +page?: number +/** + * Quantidade de produtos por página + */ +per_page?: number +/** + * Filtra pela referência + */ +reference?: string +/** + * Filtra pelo ID dos produtos + */ +ids?: string[] +/** + * Filtra produtos que coném a tag + */ +tag?: string +/** + * Filtra produtos alterados depois da data + */ +updated_after?: string +/** + * Exibe os produtos cadastrados recentemente primeiro + */ +sort?: "newest" +/** + * Inclui os produtos inativos na listagem + */ +include_inactive?: boolean +/** + * Inclui na requisição se deseja que venham todas as imagens do produto + */ +include_images?: boolean +} +response: Product[] +} +/** + * Cria um produto + */ +"POST /api/v2/products": { +body: SimpleProduct +response: { +id?: number +/** + * Indica se o produto está ativo (`true`) ou inativo (`false`) + */ +active?: (boolean & string) +/** + * Código de Referência do produto + */ +reference: string +/** + * Nome do produto + */ +name: string +/** + * Descrição do produto + */ +description?: string +/** + * Lista de tags associadas ao produto + */ +tag_list?: string[] +slug?: string +url?: string +updated_at?: string +/** + * Tipo de produto, entre: + * - `sample`: amostra + * - `subscription`: assinatura + * - `product`: produto em geral + */ +product_type?: ("product" | "sample" | "subscription") +} +} +/** + * Retorna um produto + */ +"GET /api/v2/products/:id": { +searchParams: { +/** + * Lista de cupons para calcular o desconto do produto + */ +coupon_codes?: string[] +/** + * Se "true", inclui o nome do local nos inventários das variantes + */ +include_inventory_place?: string +/** + * Se "true", inclui todas as imagens do produto + */ +include_images?: string +} +response: Product +} +/** + * Remove um produto + */ +"DELETE /api/v2/products/:id": { + +} +/** + * Atualiza um produto + */ +"PATCH /api/v2/products/:id": { +body: { +name: string +description?: string +active?: boolean +reference: string +tag_list?: string +} +} +/** + * Permite atualizar um produto pela referência + */ +"PATCH /api/v2/products/reference/:reference": { +body: { +reference: string +name: string +description?: string +active?: boolean +product_type?: ("product" | "sample" | "subscription") +} +} +/** + * Recebe uma avaliação e recalcula a pontuação atual + */ +"POST /api/v2/products/:id/rate": { +searchParams: { +/** + * Avaliação + */ +rate?: number +} +response: { +rating?: string +votes?: string +} +} +/** + * Busca os produtos de acordo com os parâmetros definidos + */ +"GET /api/v2/products/search": { +searchParams: { +/** + * Número da página + */ +page?: number +/** + * Quantidade de produtos por página + */ +per_page?: number +/** + * Filtra pelo ID dos produtos + */ +"ids[]"?: number[] +/** + * Filtra produtos que contenham o termo + */ +term?: string +/** + * Permite que o filtro 'term' realize filtragem de produtos por termo parcial + */ +wildcard?: boolean +/** + * Filtra pelo nome da tag dentro de um tipo de tag. Exemplo, type_tags[cor]=verde + */ +"type_tags[]"?: { + +} +/** + * Operador lógico para o filtro de tag + */ +type_tags_operator?: ("and" | "or") +/** + * Filtra pelo valor da propriedade 1 + */ +"property1_values[]"?: string[] +/** + * Operador lógico para o filtro de valor da propriedade 1 + */ +property1_operator?: ("and" | "or") +/** + * Filtra pelo valor da propriedade 2 + */ +"property2_values[]"?: string[] +/** + * Operador lógico para o filtro de valor da propriedade 2 + */ +property2_operator?: ("and" | "or") +/** + * Filtra pelo valor da propriedade 3 + */ +"property3_values[]"?: string[] +/** + * Operador lógico para o filtro de valor da propriedade 3 + */ +property3_operator?: ("and" | "or") +/** + * Filtra pelo preço de venda mínimo do produto + */ +min_price?: number +/** + * Filtra pelo preço de venda máximo do produto + */ +max_price?: number +/** + * Filtra pelo nome das tags, independente do tipo + */ +"tags[]"?: string[] +/** + * Filtra pelo nome das tags, independente do tipo + */ +parent_tags?: string[] +/** + * Filtra por produtos disponíveis + */ +show_only_available?: boolean +/** + * Ordena o resultado da busca de produtos conforme a opção escolhida + */ +sort?: ("newest" | "oldest" | "lowest_price" | "highest_price") +} +response: { +results?: ProductSearch[] +aggregations?: { +min_price?: number +max_price?: number +types?: { + +} +properties?: { +property1?: { +value?: string +count?: number +}[] +property2?: { +value?: string +count?: number +}[] +property3?: { +value?: string +count?: number +}[] +} +} +} +} +/** + * Retorna uma variante pelo SKU + */ +"GET /api/v2/variants/:sku": { +response: Variant +} +/** + * Permite atualizar uma variante pelo SKU + */ +"PATCH /api/v2/variants/:sku": { +body: { +sku: string +name?: string +quantity: number +main?: boolean +/** + * Largura do produto, em centímetros + */ +width?: number +/** + * Altura do produto, em centímetros + */ +height?: number +/** + * Comprimento do produito, em centímetros + */ +length?: number +/** + * Massa do produto, em gramas + */ +weight?: number +/** + * Dias de manuseio da variante + */ +handling_days?: number +/** + * Preço do item + */ +price: number +/** + * Customização da variante + */ +custom_attributes?: { + +} +min_quantity?: number +norder?: number +property1?: VariantProperty1 +property2?: VariantProperty1 +property3?: VariantProperty1 +barcode?: string +/** + * Quantidade de itens vendidos + */ +quantity_sold?: number +} +} +/** + * Lista as imagens de uma variante passando o SKU da mesma na URL + */ +"GET /api/v2/products/:productId/variants/:sku/images": { +response: { +url: string +/** + * Data e horário da última atualização da imagem do produto + */ +updated_at: string +}[] +} +/** + * Lista as imagens de uma variante passando o SKU da mesma nos parâmetros + */ +"GET /api/v2/products/:productId/variants/images": { +response: { +url: string +/** + * Data e horário da última atualização da imagem do produto + */ +updated_at: string +}[] +} +/** + * Calcula o frete para uma determinada variante + */ +"GET /api/v2/variants/:sku/shipping_methods": { +searchParams: { +quantity: number +zip: string +} +response: { +name: string +value: string +price: number +description: string +delivery_days: number +value_needed_to_discount: (null | number) +shipping_method_id: (null | number) +notice: (null | string) +fulfillment_company: (null | string) +countries: (null | { +country?: string +price?: string +}[]) +}[] +} +/** + * Permite listar as tags + */ +"GET /api/v2/tags": { +searchParams: { +/** + * Indica a quantidade de tags que devem ser listadas (page será ignorado) + */ +limit?: number +/** + * Número da página + */ +page?: number +/** + * Quantidade de resultados por página + */ +per_page?: number +/** + * Exibe somente as tags com o tipo indicado + */ +type?: string +/** + * Exibe somente as tags com um dos tipos indicados + */ +types?: string[] +/** + * Exibe somente as tags com um dos nomes indicados + */ +names?: string[] +/** + * Quando passado qualquer valor filtra as tags que contenham imagens + */ +images?: string +/** + * Quando passado qualquer valor filtra as tags marcadas para serem exibidas no carrinho + */ +show_in_carts?: string +/** + * Exibe somente as tags do produto indicado + */ +product_id?: number +/** + * Texto livre que permite filtrar as tags pelo nome + */ +name?: string +/** + * String no formato , que determina o campo a ser ordenado e qual a ordem (asc,desc) + */ +sort?: ("name,asc" | "name,desc" | "type,asc" | "type,desc" | "title,asc" | "title,desc" | "products_count,asc" | "products_count,desc") +} +response: Tag[] +} +/** + * Cria uma tag + */ +"POST /api/v2/tags": { +body: { +name: string +title?: string +/** + * Equivalente ao subtítulo + */ +blurb?: string +description?: string +tag_type?: string +show_in_carts?: boolean +} +response: Tag +} +/** + * Lista os tipos de tags usados em alguma tag + */ +"GET /api/v2/tags/types": { +searchParams: { +/** + * Número da página + */ +page?: number +/** + * Quantidade de resultados por página + */ +per_page?: number +} +response: string[] +} +/** + * Retorna uma tag + */ +"GET /api/v2/tags/:name": { +response: Tag +} +/** + * Remove uma tag + */ +"DELETE /api/v2/tags/:name": { + +} +/** + * Permite atualizar uma tag + */ +"PATCH /api/v2/tags/:name": { + +} +/** + * Retorna os dados de um cupom usando o seu código + */ +"GET /api/v2/coupon_codes/:code": { +response: { +id: number +code: string +discount_id: number +updated_at: string +} +} +/** + * Cria um pedido no Paypal para que posteriormente possa receber um pagamento + */ +"POST /api/v2/carts/:cartId/payment/paypal": { +response: { +status?: string +id?: string +links?: { +href?: string +rel?: string +method?: string +}[] +} +} +/** + * Retorna uma lista de clientes. Caso seja informado o parâmetro "email", então apenas o cliente com esse email será retornado + */ +"GET /api/v2/clients": { +searchParams: { +/** + * Retorna somente o cliente com o email informado + */ +email?: string +/** + * Número da página + */ +page?: number +/** + * Registros por página + */ +per_page?: number +/** + * Filtra os clientes pela menor data de atualização + */ +min_updated_at?: string +/** + * Filtra os clientes pela maior data de atualização + */ +max_updated_at?: string +/** + * Data de inicío da filtragem de clientes pela data de aniversário + */ +birthday_start?: string +/** + * Data final da filtragem de clientes pela data de aniversário + */ +birthday_end?: string +/** + * Filtra os clientes que possuem telefone + */ +has_phone?: string +/** + * Filtra os clientes que possuem first name + */ +has_first_name?: string +/** + * Filtra os clientes por vendedor + */ +user_id?: number +/** + * Filtra os clientes que possuem o termo em alguns dos campos + */ +term?: string +/** + * Ordena o resultado da busca de clientes conforme a opção escolhida + */ +sort?: ("name" | "birthdate") +} +response: Client[] +} +/** + * Permite criar um cliente + */ +"POST /api/v2/clients": { +body: { +email?: string +first_name?: string +last_name?: string +birthdate?: string +gender?: ("M" | "F") +/** + * separado por vírgula + */ +tags?: string +lists?: string[] +password?: string +password_confirmation?: string +terms?: boolean +} +response: { +id?: number +first_name?: string +last_name?: string +email?: string +gender?: string +phone_area?: string +phone?: string +cpf?: string +cnpj?: string +ie?: string +tags?: string +lists?: string[] +facebook_uid?: string +liked_facebook_page?: boolean +updated_at?: string +birthdate?: string +recent_address?: { +id?: string +first_name?: string +last_name?: string +company_name?: string +street_name?: string +street_number?: string +neighborhood?: string +complement?: string +reference?: string +city?: string +state?: string +zip?: string +first_phone_area?: string +first_phone?: string +second_phone_area?: string +second_phone?: string +email?: string +documents?: { +cpf?: string +cnpj?: string +} +}[] +} +} +/** + * Permite retornar as informações do cliente + * O auth_token do cliente pode ser informado no lugar do ID na URL + */ +"GET /api/v2/clients/:id": { +response: Client +} +/** + * Permite remover um cliente + */ +"DELETE /api/v2/clients/:id": { + +} +/** + * Permite atualizar as informações do cliente + */ +"PATCH /api/v2/clients/:id": { +body: { +email?: string +first_name?: string +last_name?: string +birthdate?: string +gender?: ("M" | "F") +/** + * separado por vírgula + */ +tags?: string +lists?: string[] +password?: string +password_confirmation?: string +terms?: boolean +} +} +/** + * Retorna a lista de pedidos do cliente + */ +"GET /api/v2/clients/:id/orders": { +response: Order[] +} +/** + * Lista os endereços do cliente utilizados nos pedidos que foram confirmados + */ +"GET /api/v2/clients/:id/addresses": { +searchParams: { +status?: string +} +response: Address +} +/** + * Lista os endereços cadastrados pelo cliente + */ +"GET /api/v2/clients/:clientId/registered_addresses": { +response: Client1 +} +/** + * Permite criar um endereço do cliente + */ +"POST /api/v2/clients/:clientId/registered_addresses": { +body: { +street_name?: string +street_number?: string +complement?: string +neighborhood?: string +label?: string +zip?: string +reference?: string +} +response: Client1 +} +/** + * Delete o endereço cadastrado pelo cliente + */ +"DELETE /api/v2/clients/:clientId/registered_addresses/:id": { + +} +/** + * Permite atualizar um endereço do cliente + */ +"PATCH /api/v2/clients/:clientId/registered_addresses/:id": { +body: { +street_name?: string +street_number?: string +complement?: string +neighborhood?: string +label?: string +zip?: string +reference?: string +} +response: Client1 +} +/** + * Cria uma senha para o cliente e envia por email + */ +"POST /api/v2/clients/recover_password": { +searchParams: { +/** + * Email do cliente + */ +email: string +/** + * Preencher para pular o envio do email de senha para o cliente + */ +no_send?: string +} +response: Client +} +/** + * Retorna o saldo de crétitos do cliente + */ +"GET /api/v2/clients/:id/credits": { +response: { +balance?: number +} +} +/** + * Retorna as transfertências de crétidos realizadas + */ +"GET /api/v2/clients/:id/credits/transfers": { +response: { +from?: { +account?: string +amount?: number +} +to?: { +account?: string +amount?: number +} +}[] +} +/** + * Lista os bônus do cliente que ainda não foram utilizados + */ +"GET /api/v2/clients/:id/bonuses": { +searchParams: { +/** + * Número da página + */ +page?: string +/** + * Registros por página + */ +per_page?: string +} +response: Bonus[] +} +/** + * Solicita a remoção (esquecimento) dos dados pessoais de um cliente, de acordo com a LGPD + */ +"PATCH /api/v2/clients/:id/remove_personal_data": { + +} +/** + * Faz o login do cliente pelo token salvo no campo auth_token + */ +"GET /api/v2/auth/email/:token": { +response: { +id: number +token: string +} +} +/** + * Faz o login do cliente por usuário e senha + */ +"POST /api/v2/auth/client": { +body: { +email: string +password: string +} +response: { +id: number +auth_token: string +} +} +/** + * Faz o pagamento do carrinho usando a forma de pagamento informada + */ +"POST /api/v2/carts/:cartId/payment": { +body: { +/** + * Meio de pagamento + */ +payment_method: "pix" +/** + * Canal de venda do carrinho + */ +channel?: ("ecommerce" | "direct") +} +} +/** + * Lista os menus + */ +"GET /api/v2/menus": { +searchParams: { +parent_id?: number +position?: string +} +response: Menu[] +} +/** + * Cria um menu + */ +"POST /api/v2/menus": { +body: { +label: string +tooltip?: string +description?: string +type: string +url?: string +page_id?: number +parent_id?: number +position: string +new_position?: string +external?: boolean +tag_id?: number +} +response: Menu +} +/** + * Retorna um menu + */ +"GET /api/v2/menus/:id": { +response: Menu +} +/** + * Remove um menu + */ +"DELETE /api/v2/menus/:id": { + +} +/** + * Atualiza um menu + */ +"PATCH /api/v2/menus/:id": { +body: { +label: string +tooltip?: string +description?: string +type: string +url?: string +page_id?: number +parent_id?: number +position: string +new_position?: string +external?: boolean +tag_id?: number +} +} +/** + * Lista as posições dos menus + */ +"GET /api/v2/menus/positions": { +response: string[] +} +/** + * Reordena os menus na ordem em que seus ids são listados no request + */ +"POST /api/v2/menus/reorder": { +body: { +/** + * A ordem dos elementos será replicada para os menus + */ +ids: number[] +} +} +/** + * Retorna os menus em árvore, organizados pela posição + */ +"GET /api/v2/menus/tree": { +response: { +/** + * Posição + */ +[k: string]: MenuTree[] +} +} +/** + * Retorna uma mensagem do site + */ +"GET /api/v2/site_message": { +response: SiteMessage +} +/** + * Remove uma mensagem do site + */ +"DELETE /api/v2/site_message": { + +} +/** + * Cria ou atualiza uma mensagem do site + */ +"PATCH /api/v2/site_message": { +body: { +title?: string +description?: string +call_to_action?: string +} +} +/** + * Lista as imagens associadas a loja + */ +"GET /api/v2/shop/images": { +searchParams: { +/** + * Número da página atual. Os dados de paginação estarão disponíveis, em formato JSON, no header X-Pagination no response da API, caso exista paginação + */ +page?: number +/** + * Número máximo de registros que deve ser retornado por página + */ +per_page?: number +/** + * Ordena o resultado da busca de produtos em ordem crescente de cadastro + */ +sort?: "newest" +} +response: ShopAsset[] +} +/** + * Permite cadastrar uma imagem + */ +"POST /api/v2/shop/images": { +body: { +position?: string +file_uid?: string +} +response: ShopAsset +} +/** + * Permite remover uma imagem da loja + */ +"DELETE /api/v2/shop/images/:id": { + +} +/** + * Permite adicionar um atributo customizado de produto + */ +"POST /api/v2/shop/product_attributes": { +body: { +index: number +name: string +mandatory: boolean +} +response: ProductsAttributes +} +/** + * Permite listar as personalizações + */ +"GET /api/v2/customizations": { +searchParams: { +/** + * Filtra por produto + */ +product_id?: number +} +response: Customization[] +} +/** + * Permite criar uma personalização + */ +"POST /api/v2/customizations": { +body: { +group_name: string +group_type: string +name: string +label?: string +image_uid?: string +image_name?: string +price?: number +quantity?: number +handling_days?: number +tag_id: number +sku?: string +pattern?: string +} +response: Customization +} +/** + * Permite retornar uma personalização + */ +"GET /api/v2/customizations/:id": { +response: Customization +} +/** + * Permite remover uma personalização + */ +"DELETE /api/v2/customizations/:id": { + +} +/** + * Permite alterar uma personalização + */ +"PATCH /api/v2/customizations/:id": { +body: { +group_name?: string +group_type?: string +name?: string +label?: string +image_uid?: string +image_name?: string +price?: string +quantity?: string +handling_days?: string +tag_id?: string +sku?: string +pattern?: string +} +} +/** + * Permite listar os itens do pedido + */ +"GET /api/v2/orders/:orderId/items": { +response: OrderItems[] +} +/** + * Permite listar as personalizações de cada item do pedido + */ +"GET /api/v2/orders/:orderId/items/:itemId/customizations": { +response: OrderItemCustomization[] +} +/** + * Permite listar as personalizações de cada item do carrinho + */ +"GET /api/v2/carts/:cartId/items/:itemId/customizations": { +response: { +[k: string]: CartItemCustomization[] +} +} +/** + * Permite remover uma customização do item do carrinho + */ +"DELETE /api/v2/carts/:cartId/items/:itemId/customizations": { + +} +/** + * Lista os mapeamentos + */ +"GET /api/v2/mappings": { +searchParams: { +/** + * Número da página atual. Os dados de paginação estarão disponíveis, em formato JSON, no header X-Pagination no response da API, caso exista paginação + */ +page?: number +/** + * Número máximo de registros que deve ser retornado por página + */ +per_page?: number +} +response: Mapping[] +} +/** + * Cria um mapeamento + */ +"POST /api/v2/mappings": { +body: { +key: string +from?: string[] +to?: string +} +response: Mapping +} +/** + * Retorna os dados de um mapeamento + */ +"GET /api/v2/mappings/:id": { +response: Mapping +} +/** + * Remove um mapeamento + */ +"DELETE /api/v2/mappings/:id": { + +} +/** + * Atualiza um mapeamento + */ +"PATCH /api/v2/mappings/:id": { +body: { +key: string +from?: string[] +to?: string +} +} +/** + * Retorna a lista de banners + */ +"GET /api/v2/banners": { +searchParams: { +/** + * Booleano indicando para filtrar banners fora do prazo de validade + */ +only_valid?: string +/** + * Booleano indicando para filtrar banners com prazo de validade expirados + */ +only_expired?: string +/** + * Booleano indicando para filtrar banners agendados + */ +only_scheduled?: string +/** + * Lista separada por vírgula com nomes de tags + */ +tag?: string +/** + * Texto livre que permite filtrar os banners pelo título + */ +title?: string +/** + * Booleano indicando para não fazer paginação dos resultados + */ +no_paginate?: string +/** + * Número da página atual. Os dados de paginação estarão disponíveis, em formato JSON, no header X-Pagination no response da API, caso exista paginação + */ +page?: number +/** + * Número máximo de registros que deve ser retornado por página + */ +per_page?: number +} +response: Banner[] +} +/** + * Retorna os dados de um banner + */ +"GET /api/v2/banners/:id": { +response: Banner +} +/** + * Retorna todos os banners disponíveis agrupados por tag + */ +"GET /api/v2/banners/all": { +response: { +[k: string]: SlimBanner[] +} +} +/** + * Permite calcular o frete para pedidos internacionais + */ +"GET /api/v2/carts/:cartId/shipping_methods/intl": { +searchParams: { +/** + * Código do país de destino + */ +country: string +} +response: { +"{package_label}"?: ShippingMethods1[] +} +} +/** + * Lista as amostras disponíveis para determinado carrinho + */ +"GET /api/v2/carts/:cartId/samples": { +response: { +id: number +image_url: (null | string) +name: string +reference: string +updated_at: string +url: string +variants: { +id: number +main: boolean +sku: string +name: string +updated_at: string +image_url: (null | string) +product_id: number +norder: number +}[] +} +} +/** + * Retorna o endereço de entrega + */ +"GET /api/v2/carts/:cartId/shipping_address": { +response: EnderecoDeEnvio +} +/** + * Adiciona um endereço de entrega no carrinho + */ +"POST /api/v2/carts/:cartId/shipping_address": { +body: EnderecoDeEnvio1 +response: CartItem +} +/** + * Associa um código de cupom ao carrinho + */ +"POST /api/v2/carts/:cartId/coupon_code": { +body: { +/** + * Código do cupom + */ +code: string +} +response: { +/** + * Código do cupom + */ +code: string +discount: number +rebate_token: string +rebate_discount: number +} +} +/** + * Lista todos os channels usados nos pedidos criados + */ +"GET /api/v2/orders/channels": { +response: string[] +} +/** + * Lista todos os estados usados nos pedidos criados + */ +"GET /api/v2/orders/states": { +response: string[] +} +/** + * Retorna o preço do produto e das variantes + */ +"GET /api/v2/products/:productId/price": { +searchParams: { +/** + * Array com os códigos de cupons + */ +coupon_codes?: string[] +} +response: { +available: boolean +on_sale: boolean +price: number +sale_price: number +intl_price: number +discount_rule?: any +/** + * @minItems 1 + */ +installments: [ProductInstallment, ...(ProductInstallment)[]] +/** + * Data e horário da última atualização + */ +updated_at: string +/** + * @minItems 1 + */ +variants: [ProductPriceVariant, ...(ProductPriceVariant)[]] +} +} +/** + * Lista as imagens do produto + */ +"GET /api/v2/products/:productId/images": { +response: ProductImage[] +} +/** + * Cria uma imagem do produto + */ +"POST /api/v2/products/:productId/images": { +body: { +file_url: string +/** + * IDs da variantes associadas a imagem + */ +variant_ids?: number[] +} +response: ProductImage[] +} +/** + * Deleta uma imagem do produto + */ +"DELETE /api/v2/products/:productId/images/:id": { + +} +/** + * Reordena as imagens do produto + */ +"POST /api/v2/products/:productId/images/reorder": { +body: { +ids: number[] +} +} +/** + * Associa a imagem com uma variante + */ +"POST /api/v2/products/:productId/images/:id/add_variant": { +body: { +variant_id: number +} +} +/** + * Remove a associação da imagem com uma variante + */ +"POST /api/v2/products/:productId/images/:id/remove_variant": { +body: { +variant_id: number +} +} +/** + * Retorna uma lista de pacotes de um pedido + */ +"GET /api/v2/orders/:orderCode/packages": { +response: Package[] +} +/** + * Indica para a API que dererminado evento aconteceu e que ela deve disparar as ações relacionadas + */ +"POST /api/v2/events": { +searchParams: { +/** + * Evento que ocorreu + */ +event_type: string +/** + * ID do recurso selacionado ao evento + */ +id: string +/** + * IP do usuário + */ +browser_ip?: string +/** + * User agent do usuário + */ +user_agent?: string +} +} +/** + * Permite a listagem de recebíveis (comissão) de um usuário vendedor da loja, quando ocorre split de pagamentos via Pagarme + */ +"GET /api/v2/users/:id/payables": { +response: RecebiveisDoUsuario[] +} +/** + * Retorna um produto pelo código identificador (`product_id`) + */ +"GET /api/v2/products/:productId": { +searchParams: { +/** + * Array com os códigos de cupons + */ +coupon_codes?: string[] +/** + * Selecione `true` para incluir o nome do local de armazenamento no retorno da requisição + */ +include_inventory_place?: string +/** + * Selecione `true` para incluir todas as imagens do produto + */ +include_images?: string +} +response: Product +} +/** + * Remove um produto do catálogo pelo código indentificador (`product_id`) + */ +"DELETE /api/v2/products/:productId": { + +} +/** + * Atualiza informações de um produto no catálogo pelo código identificador (`product_id`) + */ +"PATCH /api/v2/products/:productId": { +body: SimpleProduct1 +} +/** + * Recebe uma avaliação e recalcula a pontuação atual + */ +"POST /api/v2/products/:productId/rate": { +searchParams: { +/** + * Avaliação + */ +rate?: number +} +response: { +/** + * Média das avaliações + */ +rating?: string +/** + * Número de avaliações recebidas + */ +votes?: string +} +} +/** + * Permite remover uma variante + */ +"DELETE /api/v2/products/:productId/variants/:variantId": { + +} +/** + * @deprecated + * Atualiza as informações de um variante + */ +"PATCH /api/v2/products/:productId/variants/:variantId": { +body: { +sku: string +name?: string +quantity: number +main?: boolean +/** + * Massa do produto, em gramas + */ +weight?: number +/** + * Largura do produto, em centímetros + */ +width?: number +/** + * Altura do produto, em centímetros + */ +height?: number +/** + * Comprimento do produito, em centímetros + */ +length?: number +/** + * Dias de manuseio da variante + */ +handling_days?: number +price: number +/** + * Customização da variante + */ +custom_attributes?: { + +} +min_quantity?: number +norder?: number +property1?: { +name?: string +value?: string +defining?: boolean +} +property2?: { +name?: string +value?: string +defining?: boolean +} +property3?: { +name?: string +value?: string +defining?: boolean +} +barcode?: string +/** + * Quantidade de itens vendidos + */ +quantity_sold?: number +} +} +/** + * Deleta uma imagem do produto + */ +"DELETE /api/v2/products/:productId/images/:imageId": { + +} +/** + * Associa a imagem com uma variante + */ +"POST /api/v2/products/:productId/images/:imageId/add_variant": { +body: { +variant_id: number +} +} +/** + * Remove a associação da imagem com uma variante + */ +"POST /api/v2/products/:productId/images/:imageId/remove_variant": { +body: { +variant_id: number +} +} +/** + * Retorna as informações de um carrinho pelo seu `id` ou `token` + */ +"GET /api/v2/carts/:cartId": { +response: Cart1 +} +/** + * Permite excluir um carrinho + */ +"DELETE /api/v2/carts/:cartId": { + +} +/** + * Permite atualizar os atributos de um carrinho + */ +"PATCH /api/v2/carts/:cartId": { +body: ParametrosDeCarrinhoResumido +} +/** + * Remove um item do carrinho + */ +"DELETE /api/v2/carts/:cartId/items/:itemId": { + +} +/** + * Atualiza um item do carrinho + */ +"PATCH /api/v2/carts/:cartId/items/:itemId": { +body: Produto1 +} +/** + * Atualiza o método para o envio dos itens do carrinho + */ +"PATCH /api/v2/carts/:cartId/shipping_methods/:valueMethod": { +body: ShippingMethods1 +} +/** + * Calculo os método de envio disponíveis para o carrinho + */ +"GET /api/v2/carts/:cartId/shipping_methods": { +response: { +"{package_label}"?: ShippingMethods1[] +} +} +/** + * Calcula as parcelas de pagamento para valor total do carrinho + */ +"GET /api/v2/carts/:cartId/installments": { +response: CartInstallment1[] +} +/** + * Retorna os dados de um pedido pelo `code` ou `token` do pedido + */ +"GET /api/v2/orders/:orderCode": { +searchParams: { +/** + * Inclui as formas de entrega do pedido + */ +include_shipping_address?: boolean +} +response: Order +} +/** + * Atualiza o campo de dados extras de um pedido pelo `code` do pedido + */ +"PATCH /api/v2/orders/:orderCode": { +body: { +/** + * Campo para registro de observações, chave ou valores necessários + */ +extra?: { + +} +} +} +/** + * Retorna a *timeline* de eventos ocorridos em um pedido + */ +"GET /api/v2/orders/:orderCode/events": { +response: { + +}[] +} +/** + * Retorna a avaliação que o cliente fez em um pedido + */ +"GET /api/v2/orders/:orderCode/reviews": { + +} +/** + * Retorna os descontos de um pedido pelo `code` ou `token` do pedido + */ +"GET /api/v2/orders/:orderCode/discounts": { +response: { + +}[] +} +/** + * Retorna o endereço de envio pelo `code` do pedido + */ +"GET /api/v2/orders/:orderCode/shipping_address": { +response: EnderecoDeEnvio1 +} +/** + * Atualiza dados de endereço do pedido + */ +"PATCH /api/v2/orders/:orderCode/shipping_address": { +body: EnderecoDeEnvio1 +} +/** + * Captura o pagamento no adquirente para pedidos com pagamento por cartão de crédito. + */ +"POST /api/v2/orders/:orderCode/capture": { +response: { + +} +} +/** + * Confirma um pedido + */ +"POST /api/v2/orders/:orderCode/confirm": { +body: { +/** + * Parâmetro para incluir o retorno [da requisição de captura do pagamento](https://developers.vnda.com.br/reference/post-api-v2-orders-capture). + * Esse parâmetro é **obrigatório** para pedidos com pagamento por cartão de crédito. + */ +confirmation_data?: string +} +} +/** + * Faz o estorno do pagamento no adquirente do cartão de crédito + * Operação válida para pedidos pagos com cartão de crédito + */ +"POST /api/v2/orders/:orderCode/chargeback": { + +} +/** + * Altera o status do pedido para `cancelado` + */ +"POST /api/v2/orders/:orderCode/cancel": { +body: { +/** + * Parâmetro para incluir uma confirmação de estorno de pagamento para o cliente. + * Para pedidos com pagamento via cartão de crédito, é obrigatório que nesse campo seja incluído no parâmetro o retorno [da requisição de estorno de pagamento](https://developers.vnda.com.br/reference/post-api-v2-orders-order-code-chargeback). + */ +cancelation_data?: string +} +response: { + +} +} +/** + * Retorna os itens de um pedido pelo código do pedido + */ +"GET /api/v2/orders/:orderCode/items": { +response: ProdutoEmUmPedido1[][] +} +/** + * Lista as personalizações de um item do pedido pelos códigos do item e do pedido + */ +"GET /api/v2/orders/:orderCode/items/:itemId/customizations": { +response: OrderItemCustomization[] +} +/** + * Retorna os pedidos de um cliente pelo seu `id_client` + */ +"GET /api/v2/clients/:idClient/orders": { +response: Order[] +} +/** + * Retorna as notas fisicais de um pacote do pedido + */ +"GET /api/v2/orders/:orderCode/packages/:packageCode/invoices": { +response: Invoice[] +} +/** + * Inclui nota fiscal no pacote de um pedido + */ +"POST /api/v2/orders/:orderCode/packages/:packageCode/invoices": { +body: Invoice +response: Invoice +} +/** + * Remove uma nota fiscal + */ +"DELETE /api/v2/orders/:orderCode/packages/:packageCode/invoices/:number": { + +} +/** + * Atualiza uma nota fiscal + */ +"PATCH /api/v2/orders/:orderCode/packages/:packageCode/invoices/:number": { +body: Invoice +} +/** + * Permite listar os pedidos pendentes do feed + */ +"GET /api/feed/orders": { +searchParams: { +/** + * Selecione `true` para incluir o endereço na resposta + */ +include_shipping_address?: true +/** + * Filtra os pedidos por status + */ +status?: ("received" | "confirmed" | "canceled") +} +response: Order[] +} +/** + * Permite marcar os pedidos para que eles sejam filtrados da listagem do feed + */ +"POST /api/feed/orders": { +body: { +orders?: { +/** + * Código do pedido + */ +code: string +}[] +} +} +} +/** + * Modelo que representa um usuário na API + */ +export interface User { +/** + * Código identificador do usuário + */ +id?: number +/** + * Email do usuário + */ +email: string +/** + * Token de validação de usuário logado (`access_token`) + * + * O `access_token` é gerado quando o usuário loga no Admin + */ +access_token?: string +/** + * Nome do usuário + */ +name?: string +/** + * Identificador de usuários administradores + * + * Esse atributo retorna `true` para um usuário administrador do ambiente de loja + */ +admin?: boolean +/** + * Identificador de usuários que atualizaram a senha inicial + * + * Esse atributo retorna `true` para um usuário que já redefiniu sua senha pelo menos uma vez + */ +renew_password?: boolean +/** + * Código da função do usuário na loja: + * + * - Agente: `0`; + * - Gestor: `1`; + * - Local: `2`; + * - Agente Social Selling: `3`. + */ +role?: number +/** + * Tags para agrupamento de usuários + * As tags podem ser são utilizadas para direcionar promoções para determinados usuários, organizar os recebedores em uma divisão de pagamentos, definir regras de comissão + */ +tags?: string[] +/** + * Código externo do Vendedor. Esse campo é destinado para cadastrar um código de vendedor já existente em outro sistema. + */ +external_code?: string +/** + * Código de Discagem Direta a Distância (DDD) do telefone do usuário + */ +phone_area?: string +/** + * Número de telefone do usuário + */ +phone?: string +/** + * Data de inclusão do usuário no Admin + */ +created_at?: string +/** + * Data de atualização das informações do usuário + */ +updated_at?: string +} +/** + * Modelo que representa um pedido na API + */ +export interface Order { +/** + * Desconto por bônus do cliente + */ +rebate_discount: number +/** + * Código identificador `ID` do desconto por bônus + */ +rebate_token?: string +/** + * Código identificador `ID` do cliente + */ +user_id?: number +/** + * Data da última atualização do pedido + */ +updated_at: string +/** + * Lista com os códigos de rastreio dos pacotes do pedido + */ +tracking_code_list?: string[] +/** + * Código de rastreio do pacote + */ +tracking_code?: string +/** + * Valor final do pedido + */ +total: number +token: string +taxes: number +/** + * Valor da soma dos itens do pedido, desconsiderando descontos e frete. + */ +subtotal: number +/** + * Status do pedido + */ +status: ("received" | "confirmed" | "canceled") +payment_due_date?: string +slip_url?: string +slip_token?: string +slip_due_date?: string +slip: boolean +shipping_tracked_at?: string +shipping_price?: number +shipping_label?: string +/** + * Data e horário de envio do pedido + */ +shipped_at: string +/** + * Data e horário de recebimento do pedido + */ +received_at: string +payment_tid?: string +/** + * Método de pagamento do pedido + */ +payment_method: string +payment_gateway: string +payment_authorization: string +/** + * Data e horário do pagamento do pedido + */ +paid_at: string +items?: ProdutoEmUmPedido[] +/** + * Parcelas do pagamento parcelado + */ +installments?: number +/** + * Código identificador do pedido + */ +id?: number +/** + * Campo de observações do pedido + */ +extra?: { + +} +expected_delivery_date?: string +/** + * Email do cliente + */ +email: string +/** + * Valor do desconto aplicado no pedido + */ +discount_price: number +deposit: boolean +delivery_type?: string +delivery_message?: string +/** + * Dias para entrega + */ +delivery_days?: number +/** + * Data de entrega do pedido + */ +delivered_at: string +/** + * Código de cupom do pedido + */ +coupon_code: string +/** + * Data e horário de confirmação do pedido + */ +confirmed_at: string +/** + * Código do pedido + */ +code: string +/** + * Código identificador (`ID`) do cliente + */ +client_id: number +/** + * Canal de venda que originou o pedido + */ +channel: ("ecommerce" | "direct") +/** + * Código identificador do carrinho que originou o pedido + */ +cart_id: number +/** + * Data de validade do cartão de crédito + */ +card_validity: string +/** + * Número do cartão de crédito + */ +card_number: string +/** + * Retorna `true` se o método de pagamento do pedido é por cartão de crédito. + */ +card: boolean +/** + * Data e horário do cancelamento do pedido + */ +canceled_at?: string +/** + * Endereço IP de origem do pedido + */ +browser_ip: string +/** + * Agente do pedido + */ +agent?: string +affiliate_tag?: string +pix_qr_code?: string +/** + * Código de autorização do pagamento + */ +payment_authorization_code?: string +/** + * Indica se o pedido gerou bônus + */ +bonus_granted?: boolean +has_split?: boolean +/** + * Indica se o pedido foi pago usando o Pix + */ +pix: boolean +ame_qr_code?: string +/** + * Indica se o pedido foi pago usando o Ame + */ +ame: boolean +antifraud_assurance?: string +minItems?: 0 +} +/** + * Modelo de produto em um pedido + */ +export interface ProdutoEmUmPedido { +extra: { + +} +height?: number +id?: number +length?: number +original_price?: number +package?: string +picture_url?: string +place_city?: string +place_id?: number +place_name?: string +price?: number +product_id: number +product_name: string +quantity: number +reference: string +sku: string +total: number +variant_id: number +variant_name: string +weight: number +width: number +barcode?: string +} +/** + * Modelo que representa uma variante na API + */ +export interface Variant { +/** + * Código identificador da variante + */ +id?: number +/** + * Identifica se é a variante principal do produto. Para `true` a variante é principal e `false` a variante é secundária + */ +main?: boolean +/** + * Identifica se a variante está ativa em `true` e desativa em `false` + */ +available?: boolean +/** + * Código SKU da variante + */ +sku?: string +/** + * Nome da variante + */ +name?: string +/** + * Slug da URL da variante + */ +slug?: string +/** + * Quantidade mínima para venda + */ +min_quantity?: number +/** + * Quantidade física + */ +quantity?: number +quantity_sold?: number +/** + * Quantidade disponível + */ +stock?: number +/** + * Customização da variante + */ +custom_attributes?: { + +} +/** + * [Atributos](https://developers.vnda.com.br/docs/atributos-de-produto) da variante + */ +properties?: { +property1?: VariantProperty +property2?: VariantProperty +property3?: VariantProperty +} +/** + * Data e horário da última atualização da variante + */ +updated_at?: string +/** + * Preço do item + */ +price?: number +/** + * Relação das parcelas para pagamento do item parcelado + */ +installments?: number[] +/** + * Unidades reservadas e não reservadas do item + */ +available_quantity?: number +/** + * Massa do produto, em gramas + */ +weight?: number +/** + * Largura do produto, em centímetros + */ +width?: number +/** + * Altura do produto, em centímetros + */ +height?: number +/** + * Comprimento do produito, em centímetros + */ +length?: number +/** + * Dias de manuseio da variante + */ +handling_days?: number +/** + * Relação de itens por estoque (armazém) + */ +inventories?: VariantInventory[] +/** + * Preço promocional + */ +sale_price?: number +/** + * Preço internacional + */ +intl_price?: number +/** + * URL da imagem da variante + */ +image_url?: string +/** + * Código identificador `ID` do produto + */ +product_id?: number +/** + * Código de barra da variante + */ +barcode?: string +norder?: number +required?: [] +additionalProperties?: never +} +/** + * Modelo que representa uma propriedade customizada na API + */ +export interface VariantProperty { +/** + * Indica se a variante possui uma definição (`true`) ou se a variante não possui (`false`) + */ +defining: boolean +/** + * Nome da propriedade + */ +name: string +/** + * Valor da propriedade + */ +value?: string +} +/** + * Model que representa um inventory da variante + */ +export interface VariantInventory { +id: number +name: string +place_id: number +/** + * Preço do item + */ +price: number +quantity: number +/** + * Quantidade de itens vendidos + */ +quantity_sold: number +/** + * Preço promocional + */ +sale_price: number +slug: string +} +/** + * Modelo que representa um template na API + */ +export interface Template { +path: string +body?: string +updated_at: string +} +/** + * Modelo que representa um usuário na API + */ +export interface User1 { +/** + * Código identificador do usuário + */ +id?: number +/** + * Email do usuário + */ +email: string +/** + * Token de validação de usuário logado (`access_token`) + * + * O `access_token` é gerado quando o usuário loga no Admin + */ +access_token?: string +/** + * Nome do usuário + */ +name?: string +/** + * Identificador de usuários administradores + * + * Esse atributo retorna `true` para um usuário administrador do ambiente de loja + */ +admin?: boolean +/** + * Identificador de usuários que atualizaram a senha inicial + * + * Esse atributo retorna `true` para um usuário que já redefiniu sua senha pelo menos uma vez + */ +renew_password?: boolean +/** + * Código da função do usuário na loja: + * + * - Agente: `0`; + * - Gestor: `1`; + * - Local: `2`; + * - Agente Social Selling: `3`. + */ +role?: number +/** + * Tags para agrupamento de usuários + * As tags podem ser são utilizadas para direcionar promoções para determinados usuários, organizar os recebedores em uma divisão de pagamentos, definir regras de comissão + */ +tags?: string[] +/** + * Código externo do Vendedor. Esse campo é destinado para cadastrar um código de vendedor já existente em outro sistema. + */ +external_code?: string +/** + * Código de Discagem Direta a Distância (DDD) do telefone do usuário + */ +phone_area?: string +/** + * Número de telefone do usuário + */ +phone?: string +/** + * Data de inclusão do usuário no Admin + */ +created_at?: string +/** + * Data de atualização das informações do usuário + */ +updated_at?: string +} +/** + * Modelo que representa um carrinho na API + */ +export interface Cart { +id: number +email: string +shipping_method: string +items_count: number +quotation_responses_count: number +payment_responses_count: number +has_payment_responses: boolean +has_phone: boolean +updated_at: string +} +/** + * Parâmetros criação e atualização de carrinho + */ +export interface ParametrosDeCarrinhoResumido { +/** + * Agente que criou o carrinho + */ +agent?: string +/** + * Código de Endereçamento Postal (CEP) do destinatário do pedido + */ +zip?: string +/** + * Código identificador `ID` do cliente + */ +client_id?: number +/** + * Código identificador `ID` do desconto do carrinho + */ +coupon_code?: string +/** + * @deprecated + * Email do cliente + */ +email?: string +/** + * Token do desconto + */ +rebate_token?: string +/** + * Id do agente + */ +user_id?: number +} +/** + * Modelo que representa um carrinho na API + */ +export interface Cart1 { +/** + * Agente que criou o carrinho + */ +agent: string +/** + * Código identificador `ID` do endereço de cobrança do carrinho + */ +billing_address_id: number +/** + * Canal de venda que originou o carrinho + */ +channel: string +/** + * Código identificador `ID` do cliente + */ +client_id: number +/** + * Código identificador `ID` do carrinho + */ +code: string +/** + * Código de cupom de desconto utilizado no carrinho + */ +coupon_code: string +discount: Discount +/** + * @deprecated + * Valor do desconto + */ +discount_price: number +/** + * Campo para registro de observações, chave ou valores necessários + */ +extra: { + +} +/** + * Código identificador `ID` do carrinho + */ +id: number +/** + * Itens do carrinho + */ +items: CartItem[] +/** + * Unidades do item no carrinho + */ +items_count: number +/** + * Código identificador `ID` do endereço de entrega do carrinho + */ +shipping_address_id: number +/** + * Método de envio selecionado para o carrinho, como por exemplo: normal, expressa e agendada. + */ +shipping_method: string +/** + * Lista com as entregas disponíveis para os itens do carrinho de acordo com o endereço de envio + */ +shipping_methods: ShippingMethods[] +/** + * Preço de envio + */ +shipping_price: number +/** + * Valor da soma dos itens do carrinho, sem considerar descontos de cupom, carrinho e frete. + */ +subtotal: number +/** + * Token do carrinho + */ +token: string +/** + * Valor final do carrinho + */ +total: number +/** + * Valor total do carrinho para pagamento por depósito + */ +total_for_deposit: number +/** + * Valor total do carrinho para pagamento por boleto + */ +total_for_slip: number +/** + * Valor do carrinho para pagamento por PIX + */ +total_for_pix: number +/** + * Data da última atualização do carrinho + */ +updated_at: string +/** + * Código identificador `ID` do desconto por bônus + */ +rebate_token: string +/** + * Desconto por bônus do cliente + */ +rebate_discount: number +/** + * Número de dias para manuseio dos itens + */ +handling_days: number +/** + * Valor de desconto de promoções aplicadas ao subtotal do carrinho + */ +subtotal_discount: number +/** + * Valor de desconto de promoções aplicadas ao valor total do carrinho + */ +total_discount: number +installments?: CartInstallment +/** + * Código identificador `ID` do cliente + */ +user_id?: string +minItems?: 0 +} +/** + * Promoção aplicada no carrinho + */ +export interface Discount { +id: number +name: string +description: string +facebook: boolean +valid_to: ("store" | "cart") +/** + * DEPRECATED + */ +seal_uid: string +/** + * DEPRECATED + */ +seal_url: string +start_at: string +end_at: string +email: string +cpf: string +tags: string +} +/** + * Modelo que representa um item no carrinho na API + */ +export interface CartItem { +/** + * Unidades disponíveis do produto + */ +available_quantity: number +/** + * Número de dias para a entrega + */ +delivery_days: number +/** + * Campo para registro de observações, chave ou valores necessários + */ +extra: { + +} +/** + * Código identificador do local do produto + */ +place_id: number +/** + * Preço do produto + */ +price: number +/** + * Preço internacional + */ +intl_price: number +/** + * Código identificador `ID` do produto + */ +product_id: number +/** + * Nome do produto + */ +product_name: string +/** + * Código de referência do produto + */ +product_reference: string +/** + * URL do produto no e-commerce + */ +product_url: string +/** + * Unidades do produto no carrinho + */ +quantity: number +/** + * Identificador do seller + */ +seller: string +/** + * Nome do seller + */ +seller_name: string +/** + * Valor do produto sem descontos e promoções + */ +subtotal: number +/** + * Valor total do produto + */ +total: number +/** + * Data da última atualização do carrinho + */ +updated_at: string +/** + * Atributos da variante + */ +variant_attributes: { + +} +/** + * Quantidade miníma de variantes para compra + */ +variant_min_quantity: number +/** + * Nome da variante + */ +variant_name: string +/** + * Preço da variante + */ +variant_price: number +/** + * Preço internacional da variante + */ +variant_intl_price: number +variant_properties: Variant1 +/** + * Código SKU da [Variante](https://developers.vnda.com.br/docs/cat%C3%A1logo-de-produtos#produto-atributo-e-variante) + */ +variant_sku: string +/** + * Código identificador do item no carrinho + */ +id?: string +/** + * Tipo de produto + */ +product_type?: string +/** + * URL da imagem da variante + */ +image_url?: string +} +/** + * Modelo que representa uma variante na API + */ +export interface Variant1 { +/** + * Código identificador da variante + */ +id?: number +/** + * Identifica se é a variante principal do produto. Para `true` a variante é principal e `false` a variante é secundária + */ +main?: boolean +/** + * Identifica se a variante está ativa em `true` e desativa em `false` + */ +available?: boolean +/** + * Código SKU da variante + */ +sku?: string +/** + * Nome da variante + */ +name?: string +/** + * Slug da URL da variante + */ +slug?: string +/** + * Quantidade mínima para venda + */ +min_quantity?: number +/** + * Quantidade física + */ +quantity?: number +quantity_sold?: number +/** + * Quantidade disponível + */ +stock?: number +/** + * Customização da variante + */ +custom_attributes?: { + +} +/** + * [Atributos](https://developers.vnda.com.br/docs/atributos-de-produto) da variante + */ +properties?: { +property1?: VariantProperty +property2?: VariantProperty +property3?: VariantProperty +} +/** + * Data e horário da última atualização da variante + */ +updated_at?: string +/** + * Preço do item + */ +price?: number +/** + * Relação das parcelas para pagamento do item parcelado + */ +installments?: number[] +/** + * Unidades reservadas e não reservadas do item + */ +available_quantity?: number +/** + * Massa do produto, em gramas + */ +weight?: number +/** + * Largura do produto, em centímetros + */ +width?: number +/** + * Altura do produto, em centímetros + */ +height?: number +/** + * Comprimento do produito, em centímetros + */ +length?: number +/** + * Dias de manuseio da variante + */ +handling_days?: number +/** + * Relação de itens por estoque (armazém) + */ +inventories?: VariantInventory[] +/** + * Preço promocional + */ +sale_price?: number +/** + * Preço internacional + */ +intl_price?: number +/** + * URL da imagem da variante + */ +image_url?: string +/** + * Código identificador `ID` do produto + */ +product_id?: number +/** + * Código de barra da variante + */ +barcode?: string +norder?: number +required?: [] +additionalProperties?: never +} +/** + * Modelo que representa as formas de entrega na API + */ +export interface ShippingMethods { +package: string +name: string +label: string +price: string +delivery_days: string +delivery_type: string +description: string +short_description: string +fulfillment_company: string +} +/** + * Parcelas para pagamento parcelado + */ +export interface CartInstallment { +/** + * Identifica se há (`true`) ou não (`false`) juros no parcelamento + */ +interest: boolean +/** + * Taxa de juros do parcelamento + */ +interest_rate: number +/** + * Número de parcelas + */ +number: number +/** + * Valor de cada parcela + */ +price: number +/** + * Valor total das parcelas + */ +total: number +} +/** + * Modelo que representa uma parcela do total de um carrinho + */ +export interface CartInstallment1 { +/** + * Identifica se há (`true`) ou não (`false`) juros no parcelamento + */ +interest: boolean +/** + * Taxa de juros do parcelamento + */ +interest_rate: number +/** + * Número de parcelas + */ +number: number +/** + * Valor de cada parcela + */ +price: number +/** + * Valor total das parcelas + */ +total: number +} +/** + * Modelo que representa um local na API + */ +export interface Place { +id?: number +name: string +address_line_1: string +address_line_2?: string +city: string +neighborhood?: string +zip?: string +home_page?: string +latitude?: number +longitude?: number +images?: string[] +description?: string +email: string +first_phone?: string +second_phone?: string +mobile_phone?: string +only_cash?: boolean +categories?: string[] +marker_url?: string +state?: string +created_at?: string +updated_at?: string +opening_hours?: string +warehouse?: boolean +legal_name?: string +cnpj?: string +} +/** + * Modelo que representa uma nota fiscal na API + */ +export interface Invoice { +/** + * Número da nota fiscal + */ +number: number +/** + * Número de série da nota fiscal + */ +series?: number +/** + * Data e horário da criação da nota fiscal + */ +issued_at?: string +/** + * Chave da nota fiscal + */ +key?: string +volumes?: number +} +/** + * Modelo que representa um recebedor na API + */ +export interface PaymentRecipient { +id: number +percentage: number +active?: boolean +charge_processing_fee?: boolean +liable?: boolean +code?: string +name?: string +tag_name?: string +place_id?: number +recipient_id: number +tag_id?: number +user_id?: number +/** + * Indica se o frete deve ser incluído no split do pagamento + */ +include_shipping?: boolean +} +/** + * Valores que o usuário possui a receber + */ +export interface RecebiveisDoUsuario { +type?: string +status?: string +amount?: number +fee?: number +installment?: number +credit_date?: string +order_date?: string +transaction_id?: number +} +/** + * Modelo que representa um membro do público + */ +export interface AudienceMember { +id?: number +first_name?: string +last_name?: string +email: string +phone_area?: string +phone?: string +tags?: string[] +} +/** + * Modelo de carcaterística de produto para item no carrinho + */ +export interface Produto { +sku: string +quantity: number +extra?: { + +} +place_id?: number +store_coupon_code?: string +customizations?: any[] +} +/** + * Modelo que representa uma promoção na API + */ +export interface Discount1 { +/** + * Código identificador `ID` do desconto + */ +id?: number +/** + * Nome do desconto ou promoção + */ +name: string +/** + * Descrição do desconto + */ +description?: string +/** + * Data de início da regra do desconto + */ +start_at: string +/** + * Data de fim da regra do desconto + */ +end_at?: string +/** + * Indica se o desconto está habilitado (`true`) ou desabilitado (`false`) + */ +enabled: boolean +/** + * Em desuso + */ +facebook?: boolean +/** + * Indica a regra da promoção: se o desconto é aplicado na vitrine ou no carrinho da loja + */ +valid_to?: string +/** + * Email do cliente, no caso de promoções direcionadas para clientes específicos + */ +email?: string +/** + * Cadastro de Pessoa Física (CPF) do cliente, no caso de promoções direcionadas para clientes específicos + */ +cpf?: string +/** + * Tag de agrupamento de promoção + */ +tags?: string +} +/** + * Modelo que representa uma regra de desconto na API + */ +export interface DiscountRule { +id?: number +amount: number +type: ("fixed" | "percentage") +apply_to: string +min_quantity: number +product?: { +id?: number +reference?: string +name?: string +} +tag?: { +name?: string +} +combined_product?: { +id?: number +reference?: string +name?: string +} +min_subtotal: number +shipping_method?: string +shipping_rule?: ("any" | "all") +regions?: string[] +agent_tag?: string +channel?: string[] +} +/** + * Modelo que representa um cupom de desconto + */ +export interface Coupon { +id?: number +code?: string +uses_per_code?: number +uses_per_user?: number +referrer_email?: string +user_id?: number +updated_at?: string +orders_count?: number +} +/** + * Modelo que representa um produto na API + */ +export interface Product { +/** + * Código identificador `ID` do priduto + */ +id?: number +/** + * Indica se o produto está ativo (`true`) ou invativo (`false`) + */ +active?: boolean +/** + * Indica se o produto está disponível (`true`) ou indisponível (`false`) + */ +available?: boolean +category_tags?: { +/** + * Tipo de tag + */ +tag_type?: string +/** + * Nome da tag + */ +name?: string +/** + * Título da tag + */ +title?: string +}[] +/** + * Descrição do produto + */ +description?: string +/** + * Código de desconto + */ +discount_id?: number +/** + * Descrição do produto em HTML + */ +html_description?: string +/** + * URL da imagem do produto + */ +image_url?: string +/** + * Relação das parcelas para pagamento parcelado + */ +installments?: number[] +/** + * Quantidade mínima para venda do produto + */ +min_quantity?: string +/** + * Nome do produto + */ +name?: string +/** + * Indica se o produto está em promoção (`true`) ou não (`false`) + */ +on_sale?: boolean +/** + * Descrição simplificada + */ +plain_description?: string +/** + * Preço do item + */ +price?: number +/** + * Média de avaliação do produto + */ +rating?: { +rating?: number +votes?: number +} +/** + * Código de referência do produto + */ +reference?: string +/** + * Preço promocional + */ +sale_price?: number +/** + * slug do produto + */ +slug?: string +/** + * Lista de tags que o produto é associado + */ +tag_names?: string[] +/** + * Data e horário da última atualização do produto + */ +updated_at?: string +/** + * URL do produto + */ +url?: string +/** + * Variantes do produto + */ +variants?: { +"{id}"?: ProductVariant +}[] +/** + * Regras de desconto de uma promoção + */ +discount_rule: { +type: ("fixed" | "percentage") +amount: number +} +/** + * Imagens do produto + */ +images?: { +/** + * id do produto + */ +id?: number +/** + * Url do produto + */ +url?: string +/** + * Data e horário da última atualização do produto + */ +updated_at?: string +variant_ids?: { + +}[] +}[] +} +/** + * Modelo que representa uma variante na API + */ +export interface ProductVariant { +available: boolean +available_quantity: number +/** + * Customização da variante + */ +custom_attributes: { + +} +/** + * Dias de manuseio da variante + */ +handling_days: number +height: number +id?: number +/** + * URL da imagem da variante + */ +image_url: string +installments: number[] +inventories?: ProductVariantInventory[] +length: number +main: boolean +/** + * Quantidade mínima para venda + */ +min_quantity: number +/** + * Nome da variante + */ +name: string +norder: number +/** + * Preço do item + */ +price: number +product_id: number +/** + * [Atributos](https://developers.vnda.com.br/docs/atributos-de-produto) da variante + */ +properties: { +property1?: VariantProperty +property2?: VariantProperty +property3?: VariantProperty +} +quantity: number +/** + * Quantidade de itens vendidos + */ +quantity_sold?: number +/** + * Preço promocional + */ +sale_price: number +sku: string +slug: string +/** + * Quantidade de itens disponíveis + */ +stock: number +/** + * Data e horário da última atualização da variante + */ +updated_at: string +/** + * Massa do produto, em gramas + */ +weight: number +/** + * Largura do produto, em centímetros + */ +width: number +required?: [] +additionalProperties?: never +} +/** + * Modelo que representa um inventory da variante na API + */ +export interface ProductVariantInventory { +/** + * Código identificador `ID` do inventário + */ +id: number +/** + * Nome do inventário + */ +name?: string +/** + * Código identificador do local + */ +place_id: number +/** + * Nome do local + */ +place_name?: string +/** + * Preço do item + */ +price: number +/** + * Quantidade de itens no inventário + */ +quantity: number +/** + * Quantidade de itens vendidos + */ +quantity_sold: number +/** + * Preço promocional + */ +sale_price: number +/** + * Slug do inventário + */ +slug: string +/** + * Data e horário da última atualização da variante no inventário + */ +updated_at: string +/** + * Código da variante + */ +variant_id: number +/** + * Data de criação do inventário + */ +created_at: string +} +/** + * Modelo simplificado de um produto para atualização e criação + */ +export interface SimpleProduct { +name: string +description?: string +active?: boolean +reference: string +tag_list?: string +} +/** + * Modelo que representa um produto retornado via busca no Elasticsearch + */ +export interface ProductSearch { +id: number +active: boolean +available: boolean +subscription: boolean +slug: string +reference: string +reference_lowercase: string +name: string +description: string +image_url: string +url: string +tags: { +name: string +title: string +subtitle: string +description: string +importance: number +type: string +image_url: string +}[] +/** + * Preço do item + */ +price: number +on_sale: boolean +/** + * Preço promocional + */ +sale_price: number +intl_price: number +discount_id: number +discount_rule: { +type: ("fixed" | "percentage") +amount: number +} +discount: { +name: string +description: string +/** + * Em desuso + */ +facebook: boolean +valid_to: string +} +images: { +sku: string +url: string +}[] +variants: VariantProductSearch[] +installments: ProductInstallment[] +created_at: string +/** + * Data e horário da última atualização do produto + */ +updated_at: string +} +/** + * Modelo que representa uma variante retornada via busca no Elasticsearch + */ +export interface VariantProductSearch { +id: number +sku: string +sku_lowercase: string +name: string +full_name: string +main: boolean +available: boolean +image_url: string +/** + * Preço do item + */ +price: number +/** + * Preço promocional + */ +sale_price: number +intl_price: number +installments: ProductInstallment[] +/** + * Quantidade de itens disponíveis + */ +stock: number +quantity: number +/** + * Quantidade de itens vendidos + */ +quantity_sold: number +/** + * Quantidade mínima para venda + */ +min_quantity: number +available_quantity: number +/** + * Customização da variante + */ +custom_attributes: { + +} +/** + * [Atributos](https://developers.vnda.com.br/docs/atributos-de-produto) da variante + */ +properties: { +property1?: VariantPropertyProductSearch +property2?: VariantPropertyProductSearch +property3?: VariantPropertyProductSearch +} +inventories: { +name: string +slug: string +available: boolean +/** + * Preço do item + */ +price: number +/** + * Preço promocional + */ +sale_price: number +quantity: number +/** + * Quantidade de itens vendidos + */ +quantity_sold: number +place: { +id: number +name: string +} +}[] +/** + * Dias de manuseio da variante + */ +handling_days: number +barcode: string +/** + * Massa do produto, em gramas + */ +weight: number +/** + * Largura do produto, em centímetros + */ +width: number +/** + * Altura do produto, em centímetros + */ +height: number +/** + * Comprimento do produito, em centímetros + */ +length: number +required?: [] +additionalProperties?: never +} +/** + * Modelo que representa uma parcela + */ +export interface ProductInstallment { +number: number +/** + * Preço do item + */ +price: number +interest: boolean +interest_rate: number +total: number +} +/** + * Modelo que representa uma propriedade de uma variante quando retornada via Elasticsearch + */ +export interface VariantPropertyProductSearch { +name: string +value: string +defining: boolean +} +/** + * Modelo que representa uma propriedade de uma variante + */ +export interface VariantProperty1 { +/** + * Nome da propriedade + */ +name: string +/** + * Valor da propriedade + */ +value: string +/** + * Indica se a variante possui uma definição (`true`) ou se a variante não possui (`false`) + */ +defining: boolean +} +/** + * Modelo que representa uma tag na API + */ +export interface Tag { +name: string +title?: string +subtitle?: string +description?: string +type?: string +products_count?: number +image_url?: string +updated_at?: string +} +/** + * Modelo que representa um cliente na API + */ +export interface Client { +id?: number +first_name?: string +last_name?: string +email?: string +gender?: string +phone_area?: string +phone?: string +document_type?: ("CPF" | "CNPJ") +/** + * Número de documento cadastrado pelo cliente + */ +document_number?: string +cpf?: string +cnpj?: string +ie?: string +tags?: string +lists?: string[] +facebook_uid?: string +liked_facebook_page?: boolean +updated_at?: string +birthdate?: string +recent_address?: { +id?: string +first_name?: string +last_name?: string +company_name?: string +street_name?: string +street_number?: string +neighborhood?: string +complement?: string +reference?: string +city?: string +state?: string +zip?: string +first_phone_area?: string +first_phone?: string +second_phone_area?: string +second_phone?: string +email?: string +documents?: { +cpf?: string +cnpj?: string +} +}[] +auth_token?: string +last_confirmed_order_at?: string +received_orders_count?: number +confirmed_orders_count?: number +canceled_orders_count?: number +renew_password?: boolean +} +/** + * Modelo que representa um endereço na API + */ +export interface Address { +id?: number +first_name?: string +last_name?: string +company_name?: string +email?: string +documents?: { +cpf?: string +cnpj?: string +} +street_name?: string +street_number?: string +complement?: string +neighborhood?: string +first_phone_area?: string +first_phone?: string +second_phone_area?: string +second_phone?: string +reference?: string +zip?: string +city?: string +state?: string +recipient_name?: string +} +/** + * Modelo que representa os endereços cadastrados pelo cliente na API + */ +export interface Client1 { +id?: number +street_name?: string +street_number?: string +complement?: string +neighborhood?: string +label?: string +zip?: string +city?: string +state?: string +reference?: string +client_id?: number +} +/** + * Modelo que representa um bônus na API + */ +export interface Bonus { +amount?: number +token?: string +valid_from?: string +valid_thru?: string +created_at?: string +updated_at?: string +} +/** + * Modelo que representa um menu na API + */ +export interface Menu { +id?: number +label?: string +title?: string +description?: string +url?: string +external?: boolean +parent_id?: number +tag_id?: number +tag_name?: string +page_id?: number +page_slug?: string +items_count?: number +updated_at?: string +tooltip?: string +children?: Menu[] +image_url?: string +simple_url?: string +position?: string +norder?: number +type?: string +} +/** + * Modelo que representa um menu na API quando retornado pela ação de menu em árvore + */ +export interface MenuTree { +id?: number +title?: string +description?: string +external?: boolean +url?: string +tag_id?: number +page_id?: number +items_count?: number +children?: MenuTree[] +updated_at?: string +tooltip?: string +image_url?: string +simple_url?: string +norder?: number +} +/** + * Modelo que representa as mensagens do site na API + */ +export interface SiteMessage { +id?: number +title?: string +description?: string +call_to_action?: string +created_at?: string +updated_at?: string +} +/** + * Modelo que representa as imagens da loja na API + */ +export interface ShopAsset { +id?: number +position?: string +file_uid?: string +file_name?: string +updated_at?: string +} +/** + * Modelo que representa um atributo customizado de produto + */ +export interface ProductsAttributes { +index?: number +name?: string +mandatory?: boolean +updated_at?: string +} +/** + * Modelo que representa uma customização + */ +export interface Customization { +id?: number +group_name?: string +name?: string +label?: string +image_uid?: string +image_name?: string +price?: number +intl_price?: number +quantity?: number +handling_days?: number +tag_id?: number +sku?: string +pattern?: string +} +/** + * Modelo que representa a lista de itens do pedido + */ +export interface OrderItems { +id?: number +variant_id?: number +product_id?: number +quantity?: number +price?: number +weight?: number +width?: number +height?: number +length?: number +extra?: { +customization?: string +} +picture_url?: string +reference?: string +sku?: string +product_name?: string +variant_name?: string +original_price?: string +place_id?: string +place_name?: number +place_city?: number +total?: number +package?: number +has_customizations?: number +barcode?: number +} +/** + * Modelo que representa uma personalização de item do pedido na API + */ +export interface OrderItemCustomization { +/** + * Código identificador `ID` da personalização + */ +id: number +/** + * Número de tipos diferentes de personalizações em produtos do pedido + */ +number: number +/** + * Grupo em que se enquadra a personalização + */ +group_name: string +/** + * Nome do produto + */ +name: string +/** + * Preço do produto + */ +price: number +/** + * Preço internacional + */ +intl_price: number +/** + * Dias de manuseio do produto + */ +handling_days: number +/** + * Código SKU da variante de produto + */ +sku: string +} +/** + * Modelo que representa uma personalização de item do carrinho na API + */ +export interface CartItemCustomization { +/** + * Código identificador do produto + */ +id: number +group_name: string +name: string +number: number +/** + * Preço unitário + */ +price: number +/** + * Preço internacional + */ +intl_price: number +/** + * Número de dias para manuseio + */ +handling_days: number +/** + * Código SKU do produto + */ +sku: string +} +/** + * Modelo que representa um mapeamento na API + */ +export interface Mapping { +id?: number +key: string +from?: string[] +to?: string +created_at?: string +updated_at?: string +} +/** + * Modelo que representa um banner na API + */ +export interface Banner { +big_thumb: string +color: string +description: string +end_at: string +external: boolean +file_name: string +file_uid: string +html_description: string +id: number +norder: number +plain_description: string +small_thumb: string +start_at: string +subtitle: string +tag: string +title: string +updated_at: string +url: string +} +/** + * Modelo que representa um banner simplificado na API + */ +export interface SlimBanner { +id: number +tag: string +title: string +subtitle: string +description: string +url: string +external: boolean +start_at: string +end_at: string +file_url: string +norder: number +color: string +updated_at: string +} +/** + * Modelo que representa as formas de entrega na API + */ +export interface ShippingMethods1 { +/** + * Nome do tipo de entrega, como por exemplo Normal, Expressa e Agendada + */ +name: string +/** + * Identificador do método de envio + */ +value: string +/** + * Preço de envio + */ +price: number +/** + * Descrição do tipo de envio e prazo + */ +description: string +/** + * Número em dias do prazo de envio + */ +delivery_days: number +/** + * Valor restante da compra para que o carrinho fique elegível para frete grátis + */ +value_needed_to_discount?: number +/** + * Código identificador `ID` do tipo de envio + */ +shipping_method_id: number +/** + * Mensagem ou observação sobre a forma de envio + */ +notice?: string +/** + * Empresa responsável pelo envio + */ +fulfillment_company: string +} +/** + * Modelo de endereço de envio para carrinho e pedido + */ +export interface EnderecoDeEnvio { +id?: number +first_name: string +last_name: string +company_name?: string +email: string +/** + * Serão retornados apenas os campos preenchidos + */ +documents?: { +cpf?: string +cnpj?: string +ie?: string +} +street_name: string +street_number: string +complement?: string +neighborhood: string +/** + * Somente números + */ +first_phone_area: string +/** + * Somente números + */ +first_phone: string +/** + * Somente números + */ +second_phone_area?: string +/** + * Somente números + */ +second_phone?: string +reference?: string +/** + * Somente números + */ +zip: string +city: string +state: string +recipient_name?: string +} +/** + * Modelo de endereço de envio para carrinho e pedido + */ +export interface EnderecoDeEnvio1 { +/** + * Nome do cliente + */ +first_name?: string +/** + * Sobrenome do cliente + */ +last_name?: string +/** + * Nome da empresa (para clientes jurídicos) + */ +company_name?: string +/** + * Email do cliente + */ +email?: string +/** + * Código de Discagem Direta à Distância (DDD) + */ +first_phone_area?: string +/** + * Telefone do cliente + */ +first_phone?: string +/** + * Código de Discagem Direta à Distância (DDD) + */ +second_phone_area?: string +/** + * Telefone do cliente + */ +second_phone?: string +/** + * Nome do recebedor + */ +recipient_name?: { +[k: string]: any +} +/** + * Logradouro + */ +street_name?: string +/** + * Número + */ +street_number?: string +/** + * Complemento + */ +complement?: string +/** + * Bairro + */ +neighborhood?: string +/** + * Ponto de referência + */ +reference?: string +/** + * Código de Endereçamento Postal (CEP) + */ +zip: string +documents?: { +/** + * Cadastro de Pessoa Física + */ +cpf?: string +/** + * Registro Geral + */ +rg?: string +/** + * Cadastro Nacional de Pessoas Jurídicas + */ +cnpj?: string +/** + * Inscrição Estadual + */ +ie?: string +}[] +} +/** + * Modelo que representa os preços de uma variante + */ +export interface ProductPriceVariant { +/** + * Define se a variante do produto é a principal + */ +main: boolean +/** + * Código SKU da variante + */ +sku: string +/** + * Preço do item + */ +price: number +on_sale: boolean +/** + * Preço promocional + */ +sale_price: number +intl_price: number +available: boolean +/** + * [Atributos](https://developers.vnda.com.br/docs/atributos-de-produto) da variante + */ +properties: { +property1?: VariantProperty1 +property2?: VariantProperty1 +property3?: VariantProperty1 +} +/** + * Quantidade de itens disponíveis + */ +stock: number +installments: ProductInstallment[] +required?: [] +additionalProperties?: never +} +/** + * Modelo que representa uma imagem de um produto + */ +export interface ProductImage { +/** + * Código identificador `ID` da imagem + */ +id: number +/** + * URL da imagem + */ +url: string +/** + * Data e horário da última atualização da imagem do produto + */ +updated_at: string +/** + * Códigos das variantes que utilizam a imagem + */ +variant_ids: number[] +} +/** + * Modelo que representa um pacote na API + */ +export interface Package { +actual_shipping_method: string +/** + * Código identificador do pacote + */ +code: string +/** + * Data de entrega do pacote + */ +delivered_at: string +delivered_email_sent_at: string +/** + * Número de dias para entrega + */ +delivery_days: number +/** + * Tipo de envio do pacote + */ +delivery_type: string +/** + * Quantidade de dias úteis para entrega + */ +delivery_work_days: number +/** + * Transportadora + */ +fulfillment_company: string +/** + * Status de envio + */ +fulfillment_status: ("waiting" | "shipped" | "delivered") +integrated: boolean +invoiced: boolean +label: string +properties: { + +} +quoted_shipping_price: number +shipped_at: string +shipped_email_sent_at: string +shipping_label: string +shipping_name: string +shipping_price: number +total: number +/** + * Data e horário da última atualização do código de rastreio do pacote + */ +tracked_at: string +/** + * Código de rastreio do pacote + */ +tracking_code: string +required?: [] +additionalProperties?: never +} +/** + * Modelo simplificado de um produto para atualização e criação + */ +export interface SimpleProduct1 { +/** + * Código de Referência do produto + */ +reference: string +/** + * Nome do produto + */ +name: string +/** + * Descrição do produto + */ +description?: string +/** + * Indica se o produto está ativo (`true`) ou invativo (`false`) + */ +active?: boolean +/** + * Tags associadas ao produto + */ +tag_list?: string +/** + * Tipo de produto + */ +product_type?: ("product" | "sample" | "subscription") +} +/** + * Modelo de carcaterística de produto para item no carrinho + */ +export interface Produto1 { +/** + * Código SKU da variante do produto + */ +sku?: string +/** + * Unidades do produto disponíveis fisicamente + */ +quantity?: number +/** + * Campo para registro de observações, chave ou valores necessários + */ +extra?: { + +} +/** + * Código identificador do local do produto + */ +place_id?: number +/** + * Código de cupom + */ +store_coupon_code?: string +/** + * [Personalização](http://ajuda.vnda.com.br/pt-BR/articles/1763398-funcionalidades-produtos-personalizados) do produto + */ +customizations?: { +/** + * [Personalização](http://ajuda.vnda.com.br/pt-BR/articles/1763398-funcionalidades-produtos-personalizados) incluídas no Admin da loja. + * Se por exemplo a customização do produto é a cor, o parâmetro para a requisição deve ser `Color` ao invés de `CUstomization`. + */ +Customization?: string +}[] +} +/** + * Modelo de produto em um pedido + */ +export interface ProdutoEmUmPedido1 { +/** + * Dados extra do produto + */ +extra: { + +} +/** + * Altura do produto, em centímetros. + */ +height?: number +/** + * código identificador do produto + */ +id?: number +/** + * Comprimento do produito, em centímetros. + */ +length?: number +/** + * Preço original + */ +original_price?: number +/** + * Pacote do produto + */ +package?: string +/** + * URL da imagem do produto + */ +picture_url?: string +/** + * Cidade que o produto está + */ +place_city?: string +/** + * Código identificador do local do produto + */ +place_id?: number +/** + * Nome do local do produto + */ +place_name?: string +/** + * Preço do produto + */ +price?: number +product_id: number +product_name: string +/** + * Unidades do produto + */ +quantity: number +/** + * Código de referência do produto + */ +reference: string +/** + * Código SKU da variante do produto + */ +sku: string +/** + * Valor total do produto + */ +total: number +/** + * Código identificador da variante do produto + */ +variant_id: number +/** + * Nome da variante do produto + */ +variant_name: string +/** + * Massa do produto, em gramas + */ +weight: number +/** + * Largura do produto, em centímetros + */ +width: number +/** + * Código de barras do produto + */ +barcode?: string +/** + * Indica se o produto possui customização. + */ +has_customizations?: boolean +} diff --git a/vnda/utils/openapi/vnda.openapi.json b/vnda/utils/openapi/vnda.openapi.json new file mode 100644 index 000000000..cb8536cc3 --- /dev/null +++ b/vnda/utils/openapi/vnda.openapi.json @@ -0,0 +1,19471 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "API", + "version": "v2", + "contact": { + "name": "Dúvidas e suporte para API, envie um e-mail para", + "email": "produto@vnda.com.br" + }, + "description": "API versão 2 da Vnda E-commerce.\nSaiba mais no nosso [Guia de API](https://developers.vnda.com.br/docs/chave-de-acesso-e-requisicoes)", + "license": { + "name": "API Vnda", + "url": "https://www.vnda.com.br/" + } + }, + "servers": [ + { + "url": "https://api.vnda.com.br", + "description": "Servidor do ambiente de produção" + }, + { + "url": "https://api.sandbox.vnda.com.br", + "description": "Servidor do ambiente de testes" + } + ], + "paths": { + "/api/v2/products/{product_id}/videos": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "product_id", + "in": "path", + "required": true + } + ], + "get": { + "responses": { + "200": { + "description": "Retorna os videos do produto", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "url": { + "type": "string" + }, + "embed_url": { + "type": "string" + }, + "thumbnail_url": { + "type": "string" + }, + "updated_at": { + "type": "integer" + }, + "variant_ids": { + "type": "number" + } + } + } + } + } + } + } + } + } + }, + "/api/v2/seo_data": { + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "resource_type": { + "type": "string" + }, + "resource_id": { + "type": "integer" + }, + "parent_id": { + "type": "number" + } + }, + "required": [ + "id", + "resource_type", + "resource_id", + "parent_id" + ] + } + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "resource_type", + "in": "query" + }, + { + "schema": { + "type": "integer" + }, + "name": "resource_id", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "name": "type", + "in": "query" + }, + { + "schema": { + "type": "string" + }, + "name": "code", + "in": "query" + } + ] + } + }, + "/api/v2/users/authorize": { + "post": { + "summary": "User authorize", + "responses": { + "200": { + "description": "Retornado quando o access token do usuário ainda é válido e a senha está correta" + }, + "401": { + "description": "Retornado quando o access_token não é mais válido e/ou a senha está incorreta" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "access_token": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "type": "object", + "required": [ + "access_token", + "password" + ] + } + } + }, + "description": "" + }, + "description": "Permite autorizar operações usando o access_token e a senha do usuário", + "parameters": [], + "operationId": "post-api-v2-users-authorize", + "tags": [ + "Usuários" + ] + } + }, + "/api/v2/users/login": { + "post": { + "summary": "Faz o login do usuário", + "responses": { + "200": { + "description": "Quando o usuário e a senha são válidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User.v1" + } + } + } + }, + "401": { + "description": "Quando o usuário e/ou a senha não são válidos ou não foram passados" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string" + } + }, + "type": "object", + "required": [ + "email", + "password" + ] + } + } + }, + "description": "" + }, + "description": "Realiza o login do usuário a partir do email e da senha", + "parameters": [], + "operationId": "post-api-v2-users-login", + "tags": [ + "Usuários" + ] + } + }, + "/api/v2/users/logout": { + "post": { + "summary": "Faz o logout do usuário", + "responses": { + "200": { + "description": "Quando o usuário atual existe" + }, + "404": { + "description": "Quando o usuário atual não existe" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "properties": { + "access_token": { + "type": "string", + "description": "Token de validação de usuário logado\n\nO `access_token` é gerado quando o usuário loga no Admin " + } + }, + "type": "object" + } + } + }, + "description": "" + }, + "description": "Realiza o logout do usuário a partir do access_token do mesmo", + "parameters": [], + "operationId": "post-api-v2-users-logout", + "tags": [ + "Usuários" + ] + } + }, + "/api/v2/users/{id}": { + "get": { + "summary": "User", + "responses": { + "200": { + "description": "Retorna os dados do usuário", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User.v1" + }, + "examples": { + "Usuário": { + "value": { + "id": 1, + "email": "foo@vnda.com.br", + "name": null, + "admin": false, + "renew_password": false, + "role": 1, + "access_token": "706a99d0706a99d070006a99d0706a99d0706a99d0706a99d0706a99d0706a99d0", + "tags": [], + "external_code": null, + "created_at": "2019-11-06T08:50:37.130-03:00", + "updated_at": "2020-03-26T10:40:33.730-03:00" + } + } + } + } + } + } + }, + "parameters": [], + "tags": [ + "Usuários" + ], + "operationId": "get-api-v2-users-id", + "description": "Retorna os dados de um usuário pelo seu ID" + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "put": { + "summary": "Atualiza um usuário", + "operationId": "put-api-v2-users-id", + "responses": { + "204": { + "description": "Quando o usuário é atualizado" + }, + "404": { + "description": "Quando o usuário não existe", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Usuários" + ], + "description": "Atualiza um usuário", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "role_name": { + "type": "string", + "enum": [ + "Agente", + "Gestor", + "Local" + ] + }, + "password": { + "type": "string" + }, + "password_confirmation": { + "type": "string" + }, + "external_code": { + "type": "string" + }, + "phone_area": { + "type": "string", + "maxLength": 2 + }, + "phone": { + "type": "string", + "maxLength": 9 + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/api/v2/credits/rules/versions": { + "get": { + "summary": "Lista as versões da regra de bônus", + "tags": [ + "Créditos" + ], + "responses": { + "200": { + "description": "Quando as versões da regra são retornadas", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "event": { + "type": "string" + }, + "author": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "user_agent": { + "type": "string" + }, + "cart_id": { + "type": "string" + }, + "object_changes": { + "type": "string" + } + } + } + } + } + } + }, + "operationId": "get-api-v2-credits-rules-versions", + "parameters": [], + "description": "Retorna as versões da regra de bônus cadastrada" + } + }, + "/api/v2/credits/rules": { + "get": { + "summary": "Regras de bônus", + "tags": [ + "Créditos" + ], + "responses": { + "200": { + "description": "Quando as regras são retornadas", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "active": { + "type": "boolean" + }, + "minimum_subtotal": { + "type": "number", + "minimum": 0 + }, + "bonus": { + "type": "number", + "minimum": 1 + }, + "delayed_for": { + "type": "number", + "minimum": 0 + }, + "valid_for": { + "type": "number", + "minimum": 1, + "exclusiveMinimum": false + }, + "maximum_usage_factor": { + "type": "number", + "minimum": 0, + "maximum": 1, + "exclusiveMaximum": false, + "exclusiveMinimum": true + } + }, + "required": [ + "active", + "minimum_subtotal", + "bonus", + "delayed_for", + "valid_for", + "maximum_usage_factor" + ] + }, + "examples": { + "example-1": { + "value": { + "active": true, + "minimum_subtotal": 100, + "bonus": 10, + "delayed_for": 5, + "valid_for": 30, + "maximum_usage_factor": 0.3 + } + } + } + } + } + } + }, + "operationId": "get-api-v2-credits-rules", + "parameters": [], + "description": "Retorna as regras de bônus cadastradas" + }, + "put": { + "summary": "Atualiza a regras de bônus", + "operationId": "put-api-v2-credits-rules", + "responses": { + "204": { + "description": "Quando a regra é atualizada" + } + }, + "description": "Permite atualizar as regras de bônus", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "bonus": { + "type": "number", + "format": "float", + "minimum": 1, + "exclusiveMinimum": false, + "description": "Percentual em cima do total do pedido que vai ser dado de bônus para o cliente" + }, + "valid_in": { + "type": "integer", + "description": "Número de dias em que o crédito começa a valer", + "minimum": 0 + }, + "valid_for": { + "type": "integer", + "description": "Número de dias para a expiração do crédito", + "minimum": 1 + }, + "minimum_subtotal": { + "type": "number", + "description": "Valor mínimo do pedido para que o bônus possa ser transferido para o cliente", + "format": "float", + "minimum": 1, + "exclusiveMinimum": false + }, + "maximum_usage_factor": { + "type": "number", + "default": 1, + "minimum": 0, + "exclusiveMinimum": true, + "maximum": 1, + "description": "Percentual do subtotal do pedido que pode ser pago com o bônus" + } + }, + "required": [ + "bonus", + "valid_in", + "valid_for" + ] + } + } + }, + "description": "Parâmetros" + }, + "parameters": [], + "tags": [ + "Créditos" + ] + }, + "delete": { + "summary": "Remove as regras de bônus", + "operationId": "delete-api-v2-credits-rules", + "responses": { + "204": { + "description": "Quando o bônus é removido" + } + }, + "tags": [ + "Créditos" + ], + "description": "Permite remover as regras de bônus, desativando o recurso" + } + }, + "/api/v2/orders/{code}": { + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 10, + "maxLength": 64 + }, + "name": "code", + "in": "path", + "required": true, + "description": "O \"code\" do pedido ou o \"token\"" + }, + { + "schema": { + "type": "boolean", + "default": false + }, + "in": "query", + "name": "include_customizations_in_total", + "description": "Inclui o preço dos produtos customizados no total do pedido" + } + ], + "get": { + "summary": "Retorna um pedido", + "tags": [ + "Pedidos" + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order.v1" + } + } + } + } + }, + "operationId": "get-api-v2-orders-code", + "description": "Retorna os dados de um pedido usando o `code` ou `token`", + "parameters": [ + { + "schema": { + "type": "boolean", + "default": false + }, + "in": "query", + "name": "include_shipping_address", + "description": "Retorna as formas de entrega do pedido" + } + ] + } + }, + "/api/v2/orders": { + "get": { + "summary": "Lista os pedidos", + "tags": [ + "Pedidos" + ], + "responses": { + "200": { + "description": "Quando os pedidos são retornados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Order.v1" + } + } + } + } + }, + "404": { + "description": "Domínio de loja não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-orders", + "description": "Retorna uma lista de pedidos", + "parameters": [ + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/finish" + }, + { + "$ref": "#/components/parameters/invoiced" + }, + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/per_page" + }, + { + "$ref": "#/components/parameters/coupon_codes" + }, + { + "$ref": "#/components/parameters/include_customizations_in_total" + } + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/start" + }, + { + "$ref": "#/components/parameters/finish" + }, + { + "$ref": "#/components/parameters/invoiced" + }, + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/per_page" + }, + { + "$ref": "#/components/parameters/coupon_codes" + }, + { + "$ref": "#/components/parameters/include_customizations_in_total" + } + ] + }, + "/api/v2/orders/{code}/capture": { + "post": { + "summary": "Captura", + "operationId": "post-api-v2-orders-capture", + "responses": { + "200": { + "description": "Quando a captura foi realizada com sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + }, + "examples": { + "Pagar.me": { + "value": { + "object": "transaction", + "status": "paid", + "refuse_reason": null, + "status_reason": "acquirer", + "acquirer_response_code": "0000", + "acquirer_name": "pagarme", + "acquirer_id": "5eab10915eab10915eab1091", + "authorization_code": "123456", + "soft_descriptor": "", + "tid": 1234567, + "nsu": 1234567, + "date_created": "2020-05-14T19:14:50.322Z", + "date_updated": "2020-05-15T14:19:34.699Z", + "amount": 1400, + "authorized_amount": 1400, + "paid_amount": 1400, + "refunded_amount": 0, + "installments": 1, + "id": 1234567, + "cost": 120, + "card_holder_name": "John Doe", + "card_last_digits": "6565", + "card_first_digits": "470373", + "card_brand": "visa", + "card_pin_mode": null, + "card_magstripe_fallback": false, + "cvm_pin": false, + "postback_url": "https://demo.vnda.com.br/v2/payments/pagarme/notifications", + "payment_method": "credit_card", + "capture_method": "ecommerce", + "antifraud_score": null, + "boleto_url": null, + "boleto_barcode": null, + "boleto_expiration_date": null, + "referer": "api_key", + "ip": "127.0.0.1", + "subscription_id": null, + "phone": null, + "address": null, + "customer": { + "object": "customer", + "id": 2954669, + "external_id": "example@vnda.com.br", + "type": "individual", + "country": "br", + "document_number": null, + "document_type": "cpf", + "name": "John Doe", + "email": "example@vnda.com.br", + "phone_numbers": [ + "+5511111111111" + ], + "born_at": null, + "birthday": null, + "gender": null, + "date_created": "2020-05-14T19:14:50.248Z", + "documents": [ + { + "object": "document", + "id": "doc_cka75cka75cka75cka75cka75", + "type": "cpf", + "number": 191 + } + ] + }, + "billing": { + "object": "billing", + "id": 1255695, + "name": "John Doe", + "address": { + "object": "address", + "street": "Rua João Neves da Fontoura", + "complementary": null, + "street_number": "1", + "neighborhood": "Azenha", + "city": "Porto Alegre", + "state": "RS", + "zipcode": "90050030", + "country": "br", + "id": 2808888 + } + }, + "shipping": null, + "items": [ + { + "object": "item", + "id": "05.01.4.1.006", + "title": "Aceto Balsâmico Di Modena IGP 500ml Aceto Balsamico Di Modena IGP 500ml", + "unit_price": 1400, + "quantity": 1, + "category": null, + "tangible": true, + "venue": null, + "date": null + } + ], + "card": { + "object": "card", + "id": "card_cka75cka75cka75cka75cka75", + "date_created": "2020-05-14T19:14:50.307Z", + "date_updated": "2020-05-14T19:14:50.717Z", + "brand": "visa", + "holder_name": "f dc", + "first_digits": "470373", + "last_digits": "6565", + "country": "RUSSIA", + "fingerprint": "cka75cka75cka75cka75cka75", + "valid": true, + "expiration_date": "0423" + }, + "split_rules": null, + "metadata": { + "order": "7A4F490570", + "seller-1": { + "name": "default", + "package": "7A4F490570-01" + } + }, + "antifraud_metadata": {}, + "reference_key": null, + "device": null, + "local_transaction_id": null, + "local_time": null, + "fraud_covered": false, + "fraud_reimbursed": null, + "order_id": null, + "risk_level": "very_low", + "receipt_url": null, + "payment": null, + "addition": null, + "discount": null, + "private_label": null + } + } + } + } + } + }, + "422": { + "description": "Quando não foi possível realizar a captura junto ao adquirente", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "examples": { + "failure": { + "value": { + "error": "Capture was unsuccessful" + } + } + } + } + } + } + }, + "description": "Faz a captura do pagamento no adquirente\nApenas para pedidos pagos com cartão de crédito", + "tags": [ + "Pedidos" + ] + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "code", + "in": "path", + "required": true + } + ] + }, + "/api/v2/orders/{code}/confirm": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "code", + "in": "path", + "required": true + } + ], + "post": { + "summary": "Confirma", + "operationId": "post-api-v2-orders-code-confirm", + "responses": { + "200": { + "description": "OK" + } + }, + "description": "Altera o status do pedido para \"confirmado\"", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "confirmation_data": { + "type": "string", + "description": "Para cartão de crédito deve ser enviado OBRIGATORIAMENTE o retorno da requisição para \"/api/v2/orders/{code}/capture\"" + } + } + }, + "examples": { + "Depósito": { + "value": { + "banco": "Banco do Brasil", + "data_credito": "2020-03-26", + "conferido_por": "Nome do usuário do financeiro" + } + }, + "Cartão de crédito via Pagar.me": { + "value": { + "object": "transaction", + "status": "paid", + "refuse_reason": null, + "status_reason": "acquirer", + "acquirer_response_code": "0000", + "acquirer_name": "pagarme", + "acquirer_id": "5eab10915eab10915eab1091", + "authorization_code": "123456", + "soft_descriptor": "", + "tid": 1234567, + "nsu": 1234567, + "date_created": "2020-05-14T19:14:50.322Z", + "date_updated": "2020-05-15T14:19:34.699Z", + "amount": 1400, + "authorized_amount": 1400, + "paid_amount": 1400, + "refunded_amount": 0, + "installments": 1, + "id": 1234567, + "cost": 120, + "card_holder_name": "John Doe", + "card_last_digits": "6565", + "card_first_digits": "470373", + "card_brand": "visa", + "card_pin_mode": null, + "card_magstripe_fallback": false, + "cvm_pin": false, + "postback_url": "https://demo.vnda.com.br/v2/payments/pagarme/notifications", + "payment_method": "credit_card", + "capture_method": "ecommerce", + "antifraud_score": null, + "boleto_url": null, + "boleto_barcode": null, + "boleto_expiration_date": null, + "referer": "api_key", + "ip": "127.0.0.1", + "subscription_id": null, + "phone": null, + "address": null, + "customer": { + "object": "customer", + "id": 2954669, + "external_id": "example@vnda.com.br", + "type": "individual", + "country": "br", + "document_number": null, + "document_type": "cpf", + "name": "John Doe", + "email": "example@vnda.com.br", + "phone_numbers": [ + "+5511111111111" + ], + "born_at": null, + "birthday": null, + "gender": null, + "date_created": "2020-05-14T19:14:50.248Z", + "documents": [ + { + "object": "document", + "id": "doc_cka75cka75cka75cka75cka75", + "type": "cpf", + "number": 191 + } + ] + }, + "billing": { + "object": "billing", + "id": 1255695, + "name": "John Doe", + "address": { + "object": "address", + "street": "Rua João Neves da Fontoura", + "complementary": null, + "street_number": "1", + "neighborhood": "Azenha", + "city": "Porto Alegre", + "state": "RS", + "zipcode": "90050030", + "country": "br", + "id": 2808888 + } + }, + "shipping": null, + "items": [ + { + "object": "item", + "id": "05.01.4.1.006", + "title": "Aceto Balsâmico Di Modena IGP 500ml Aceto Balsamico Di Modena IGP 500ml", + "unit_price": 1400, + "quantity": 1, + "category": null, + "tangible": true, + "venue": null, + "date": null + } + ], + "card": { + "object": "card", + "id": "card_cka75cka75cka75cka75cka75", + "date_created": "2020-05-14T19:14:50.307Z", + "date_updated": "2020-05-14T19:14:50.717Z", + "brand": "visa", + "holder_name": "f dc", + "first_digits": "470373", + "last_digits": "6565", + "country": "RUSSIA", + "fingerprint": "cka75cka75cka75cka75cka75", + "valid": true, + "expiration_date": "0423" + }, + "split_rules": null, + "metadata": { + "order": "7A4F490570", + "seller-1": { + "name": "default", + "package": "7A4F490570-01" + } + }, + "antifraud_metadata": {}, + "reference_key": null, + "device": null, + "local_transaction_id": null, + "local_time": null, + "fraud_covered": false, + "fraud_reimbursed": null, + "order_id": null, + "risk_level": "very_low", + "receipt_url": null, + "payment": null, + "addition": null, + "discount": null, + "private_label": null + } + } + } + } + }, + "description": "" + }, + "tags": [ + "Pedidos" + ] + } + }, + "/api/v2/orders/{code}/chargeback": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "code", + "in": "path", + "required": true + } + ], + "post": { + "summary": "Estorna", + "operationId": "post-api-v2-orders-code-chargeback", + "responses": { + "200": { + "description": "OK" + }, + "422": { + "description": "Unprocessable Entity" + } + }, + "description": "Faz o estorno do pagamento no adquirente\nApenas para pedidos pagos com cartão de crédito", + "tags": [ + "Pedidos" + ] + } + }, + "/api/v2/orders/{code}/cancel": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "code", + "in": "path", + "required": true + } + ], + "post": { + "summary": "Cancela", + "operationId": "post-api-v2-orders-code-cancel", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "description": "Altera o status do pedido para \"cancelado\"", + "tags": [ + "Pedidos" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cancelation_data": { + "type": "string", + "description": "Deve ser enviado algo que comprove que o pagamento foi devolvido.\nPara cartão de crédito deve ser enviado OBRIGATORIAMENTE o retorno da requisição para \"/api/v2/orders/{code}/chargeback\"" + } + } + } + } + } + } + } + }, + "/api/v2/orders/{order_code}/packages/{package_code}/ship": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "order_code", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "package_code", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Altera para enviado", + "operationId": "patch-api-v2-orders-code-packages-code-ship", + "responses": { + "204": { + "description": "No Content" + }, + "404": { + "description": "Pedido ou rastreio não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "description": "Altera o status do pacote para \"enviado\"", + "tags": [ + "Pacotes" + ], + "parameters": [] + } + }, + "/api/v2/orders/{order_code}/packages/{package_code}/deliver": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "order_code", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "package_code", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Altera para entregue", + "operationId": "patch-api-v2-orders-code-packages-code-deliver", + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Pedido ou rastreio não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "description": "Altera o pacote para \"entregue\"", + "tags": [ + "Pacotes" + ] + } + }, + "/api/v2/variants/quantity": { + "post": { + "summary": "Atualiza em lote", + "operationId": "post-api-v2-variants-quantity", + "responses": { + "200": { + "description": "OK" + } + }, + "tags": [ + "Estoque" + ], + "description": "Recebe uma lista JSON com os SKUs que devem ser atualizados. A atualização será executada em segundo plano em aproximadamente 1 minuto ", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "place_id": { + "type": "integer", + "description": "Informe somente para atualizar o estoque de um local específico" + } + }, + "required": [ + "sku", + "quantity" + ] + } + }, + "examples": { + "Exemplo": { + "value": [ + { + "sku": "21390", + "quantity": 12, + "place_id": 1 + }, + { + "sku": "21827", + "quantity": 12 + } + ] + } + } + } + }, + "description": "" + } + } + }, + "/api/v2/variants/{sku}/quantity": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "sku", + "in": "path", + "required": true + } + ], + "post": { + "summary": "Atualiza", + "operationId": "post-api-v2-variants-sku-quantity", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + } + }, + "examples": { + "Exemplo": { + "value": { + "status": "ok" + } + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "examples": { + "Exemplo": { + "value": { + "status": "error", + "message": "A quantity must be provided to update stock" + } + } + } + } + } + } + }, + "tags": [ + "Estoque" + ], + "description": "Atualiza o estoque de uma variante de um produto", + "parameters": [ + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "quantity", + "required": true + } + ] + } + }, + "/api/v2/variants/{sku}/inventories/{place_id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "sku", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "place_id", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Atualiza um local", + "operationId": "patch-api-v2-variants-sku-inventories-place_id", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + } + } + }, + "examples": { + "Exemplo": { + "value": { + "status": "ok" + } + } + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "examples": { + "Exemplo": { + "value": { + "status": "error", + "message": "Quantity or price must be provided to update stock" + } + } + } + } + } + } + }, + "tags": [ + "Estoque" + ], + "description": "Atualiza o estoque específico de um local", + "parameters": [ + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "quantity" + } + ] + } + }, + "/api/v2/products/{product_id}/variants": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "product_id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Lista as variantes", + "tags": [ + "Variantes" + ], + "responses": { + "200": { + "description": "Quando as variantes são listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Variant" + } + }, + "examples": { + "example-1": { + "value": [ + { + "id": 95, + "main": true, + "available": true, + "sku": "123", + "name": "Variation", + "slug": "variation", + "min_quantity": 1, + "quantity": 1, + "quantity_sold": 0, + "stock": 1, + "custom_attributes": {}, + "properties": {}, + "updated_at": "2020-10-27T11:54:32.018-03:00", + "price": 10, + "installments": [ + 10 + ], + "available_quantity": 1, + "weight": 0.001, + "width": 1, + "height": 1, + "length": 1, + "handling_days": 0, + "inventories": [], + "sale_price": 10, + "image_url": "//b0.vnda.com.br/x120/shop/2014/07/08/variation.jpg", + "product_id": 6, + "barcode": null, + "norder": 1 + }, + { + "id": 27, + "main": false, + "available": true, + "sku": "13001", + "name": "Tamanho: PP | Cor: Branca", + "slug": "camiseta", + "min_quantity": 1, + "quantity": 85, + "stock": 83, + "custom_attributes": { + "size": "PP", + "color": "#FFFFFF" + }, + "properties": {}, + "updated_at": "2019-08-01T18:36:52.718-03:00", + "price": 169.9, + "installments": [ + 169.9 + ], + "available_quantity": 83, + "weight": 0.1, + "width": 11, + "height": 2, + "length": 16, + "handling_days": 0, + "inventories": [], + "sale_price": 169.9, + "image_url": "//b0.vnda.com.br/x120/shop/2014/07/08/camiseta.jpg", + "product_id": 6, + "barcode": null, + "norder": 1 + } + ] + } + } + } + } + }, + "400": { + "description": "Parâmetros enviados inválidos", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "examples": { + "example-1": { + "value": { + "error": "invalid rate value" + } + } + } + } + } + } + }, + "operationId": "get-api-v2-products-product_id-variants", + "description": "Permite listar as variantes de um produto" + }, + "post": { + "summary": "Cria uma variante", + "operationId": "post-api-v2-products-product_id-variants", + "responses": { + "201": { + "description": "Quando a variante é criada", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "main": { + "type": "boolean" + }, + "available": { + "type": "boolean" + }, + "sku": { + "type": "string" + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "min_quantity": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "stock": { + "type": "integer", + "description": "Quantidade de itens disponíveis" + }, + "custom_attributes": { + "type": "object", + "description": "Customização da variante" + }, + "properties": { + "type": "object" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Data e horário da última atualização" + }, + "price": { + "type": "integer" + }, + "installments": { + "type": "array", + "items": { + "type": "integer" + } + }, + "available_quantity": { + "type": "integer" + }, + "weight": { + "type": "number", + "description": "Massa do produto, em gramas" + }, + "width": { + "type": "number", + "description": "Largura do produto, em centímetros" + }, + "height": { + "type": "number", + "description": "Altura do produto, em centímetros" + }, + "length": { + "type": "number", + "description": "Comprimento do produito, em centímetros" + }, + "handling_days": { + "type": "number", + "description": "Dias de manuseio da variante" + }, + "inventories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Variant_inventory.v1" + } + }, + "sale_price": { + "type": "number" + }, + "image_url": { + "type": "string" + }, + "product_id": { + "type": "integer" + }, + "norder": { + "type": "integer" + } + } + }, + "examples": { + "example-1": { + "value": { + "id": 95, + "main": false, + "available": true, + "sku": "SHOP0001", + "name": "Variation", + "slug": "variation", + "min_quantity": 1, + "quantity": 1, + "stock": 1, + "custom_attributes": {}, + "properties": {}, + "updated_at": "2020-10-27T11:54:32.018-03:00", + "price": 10, + "installments": [ + 10 + ], + "available_quantity": 1, + "weight": 0.001, + "width": 1, + "height": 1, + "length": 1, + "handling_days": 0, + "inventories": [], + "sale_price": 10, + "image_url": "//b0.vnda.com.br/x120/shop/2014/07/08/variation.jpg", + "product_id": 6, + "norder": 1 + } + } + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Variantes" + ], + "description": "Permite criar uma variante", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "name": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "main": { + "type": "boolean" + }, + "width": { + "type": "number", + "description": "Largura do produto, em centímetros" + }, + "height": { + "type": "number", + "description": "Altura do produto, em centímetros" + }, + "length": { + "type": "number", + "description": "Comprimento do produito, em centímetros" + }, + "weight": { + "type": "number", + "description": "Massa do produto, em gramas" + }, + "handling_days": { + "type": "integer", + "description": "Dias de manuseio da variante" + }, + "price": { + "type": "number" + }, + "custom_attributes": { + "type": "object", + "description": "Customização da variante" + }, + "min_quantity": { + "type": "integer" + }, + "norder": { + "type": "integer" + }, + "property1": { + "type": "string", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "defining": { + "type": "boolean" + } + } + }, + "property2": { + "type": "string", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "defining": { + "type": "boolean" + } + } + }, + "property3": { + "type": "string", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "defining": { + "type": "boolean" + } + } + }, + "barcode": { + "type": "string" + } + }, + "required": [ + "sku", + "quantity", + "price" + ] + } + } + } + } + } + }, + "/api/v2/products/{product_id}/variants/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "product_id", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Atualiza uma variante", + "operationId": "patch-api-v2-products-product_id-variants-id", + "responses": { + "204": { + "description": "Quando a variante é atualizada" + }, + "404": { + "description": "Quando a variante não existe", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Variantes" + ], + "description": "Permite atualizar uma variante", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "name": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "main": { + "type": "boolean" + }, + "width": { + "type": "number" + }, + "height": { + "type": "number" + }, + "length": { + "type": "number" + }, + "weight": { + "type": "number" + }, + "handling_days": { + "type": "integer" + }, + "price": { + "type": "number" + }, + "custom_attributes": { + "type": "object" + }, + "min_quantity": { + "type": "integer" + }, + "norder": { + "type": "integer" + }, + "property1": { + "type": "string" + }, + "property2": { + "type": "string" + }, + "property3": { + "type": "string" + }, + "barcode": { + "type": "string" + }, + "quantity_sold": { + "type": "integer" + } + }, + "required": [ + "sku", + "quantity", + "price" + ] + } + } + } + }, + "deprecated": true + }, + "delete": { + "summary": "Remove uma variante", + "operationId": "delete-api-v2-products-product_id-variants-id", + "responses": { + "204": { + "description": "Quando a variante é removida" + }, + "404": { + "description": "Quando a variante não existe", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Variantes" + ], + "description": "Permite remover uma variante" + } + }, + "/api/v2/variants/reorder": { + "post": { + "summary": "Reordena as variantes", + "operationId": "post-api-v2-variants-reorder", + "responses": { + "200": { + "description": "Quando as variantes são reordenadas" + } + }, + "tags": [ + "Variantes" + ], + "description": "Permite determinar a ordem das variantes dentro de cada produto", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "description": "A ordem dos elementos será replicada para as variantes", + "items": { + "type": "integer" + } + } + }, + "required": [ + "ids" + ] + }, + "examples": { + "example-1": { + "value": { + "ids": [ + 32, + 29, + 28, + 31, + 30, + 27 + ] + } + } + } + } + } + } + } + }, + "/api/v2/templates/{path}": { + "get": { + "summary": "Retorna um template", + "tags": [ + "Templates" + ], + "responses": { + "200": { + "description": "Quando um template é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Template.v1" + }, + "examples": { + "Template": { + "value": { + "path": "home.liquid", + "body": "

shop site!

", + "updated_at": "2020-05-17T21:37:38.000-03:00" + } + }, + "Partial": { + "value": { + "path": "partials/components/product_block/_images_by_gender.liquid", + "body": "

partial template

", + "updated_at": "2020-05-17T21:37:38.000-03:00" + } + } + } + } + } + }, + "404": { + "description": "Quando um template não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "operationId": "get-api-v2-templates-path", + "description": "Retorna um template usando o path dele", + "parameters": [] + }, + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "[0-9A-Za-z_\\.\\/]+" + }, + "name": "path", + "in": "path", + "required": true, + "description": "Caminho relativo do template" + } + ], + "patch": { + "summary": "Atualiza um template", + "operationId": "patch-api-v2-templates-path", + "responses": { + "204": { + "description": "Quando um template é atualizado com sucesso" + }, + "404": { + "description": "Quando um template não é contrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "tags": [ + "Templates" + ], + "parameters": [], + "description": "Atualiza o conteúdo de um template usando o path dele", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "body": { + "type": "string" + } + } + }, + "examples": { + "Template": { + "value": { + "body": "

shop site!

" + } + }, + "Partial": { + "value": { + "body": "

partial template

" + } + } + } + } + } + } + }, + "delete": { + "summary": "Remove um template", + "operationId": "delete-api-v2-templates-path", + "responses": { + "204": { + "description": "Quando um template é excluído com sucesso" + }, + "404": { + "description": "Quando um template não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "description": "Remove um template usando o path dele", + "tags": [ + "Templates" + ], + "parameters": [] + } + }, + "/api/v2/templates": { + "post": { + "summary": "Cria um template", + "operationId": "post-api-v2-templates", + "responses": { + "201": { + "description": "Quando um template é criado com sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Template.v1" + }, + "examples": { + "Template": { + "value": { + "path": "home.liquid", + "body": "

shop site!

", + "updated_at": "2020-05-17T21:37:38.000-03:00" + } + }, + "Partial": { + "value": { + "path": "partials/components/product_block/_images_by_gender.liquid", + "body": "

partial template

", + "updated_at": "2020-05-17T21:37:38.000-03:00" + } + } + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + }, + "examples": { + "Parâmetro `path` em branco": { + "value": { + "errors": { + "path": [ + "não pode ficar em branco" + ] + } + } + } + } + } + } + } + }, + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "body": { + "type": "string" + } + }, + "required": [ + "path" + ] + }, + "examples": { + "Template": { + "value": { + "path": "home.liquid", + "body": "

shop site!

" + } + }, + "Partial": { + "value": { + "path": "partials/components/product_block/_images_by_gender.liquid", + "body": "

partial template

" + } + } + } + } + } + }, + "tags": [ + "Templates" + ], + "description": "Cria um novo template" + }, + "get": { + "summary": "Lista os templates", + "operationId": "get-api-v2-templates", + "responses": { + "200": { + "description": "Quando os templates são listados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Template.v1" + } + }, + "examples": { + "Template": { + "value": [ + { + "path": "home.liquid", + "body": "

shop site!

", + "updated_at": "2020-05-17T21:37:38.000-03:00" + } + ] + }, + "Partial": { + "value": [ + { + "path": "partials/components/product_block/_images_by_gender.liquid", + "body": "

partial template

", + "updated_at": "2020-05-17T21:37:38.000-03:00" + } + ] + }, + "Sem templates": { + "value": [] + } + } + } + } + } + }, + "description": "Retorna uma lista de templates", + "parameters": [], + "tags": [ + "Templates" + ] + } + }, + "/api/v2/users/{id}/activate": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "post": { + "summary": "Ativar", + "operationId": "post-api-v2-users-id-activate", + "responses": { + "200": { + "description": "Quando a ativação foi realizada com sucesso" + }, + "404": { + "description": "Quando o usuário não é encontrado" + } + }, + "description": "Reativa um usuário que estiver desativado", + "tags": [ + "Usuários" + ] + } + }, + "/api/v2/users/{id}/deactivate": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "post": { + "summary": "Desativar", + "operationId": "post-api-v2-users-id-deactivate", + "responses": { + "200": { + "description": "Quando a desativação foi realizada com sucesso" + }, + "404": { + "description": "Quando o usuário não é encontrado" + } + }, + "tags": [ + "Usuários" + ], + "description": "Desativa um usuário" + } + }, + "/api/v2/users": { + "get": { + "summary": "Lista os usuários", + "operationId": "get-api-v2-users", + "responses": { + "200": { + "description": "Quando os usuários são listados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User.v1", + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Código identificador do usuário" + }, + "email": { + "type": "string", + "format": "email", + "description": "Email do usuário" + }, + "access_token": { + "type": "string", + "description": "Token de validação de usuário logado (`access_token`)\n \nO `access_token` é gerado quando o usuário loga no Admin" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Nome do usuário" + }, + "admin": { + "type": "boolean", + "description": "Identificador de usuários administradores\n\nEsse atributo retorna `true` para um usuário administrador do ambiente de loja" + }, + "renew_password": { + "type": "boolean", + "description": "Identificador de usuários que atualizaram a senha inicial\n\nEsse atributo retorna `true` para um usuário que já redefiniu sua senha pelo menos uma vez" + }, + "role": { + "type": "integer", + "description": "Código da função do usuário na loja:\n\n - Agente: `0`;\n - Gestor: `1`;\n - Local: `2`;\n - Agente Social Selling: `3`." + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Tags para agrupamento de usuários\nAs tags podem ser são utilizadas para direcionar promoções para determinados usuários, organizar os recebedores em uma divisão de pagamentos, definir regras de comissão" + }, + "external_code": { + "type": "string", + "nullable": true, + "description": "Código externo do Vendedor. Esse campo é destinado para cadastrar um código de vendedor já existente em outro sistema." + }, + "phone_area": { + "type": "string", + "maxLength": 2, + "minLength": 2, + "description": "Código de Discagem Direta a Distância (DDD) do telefone do usuário" + }, + "phone": { + "type": "string", + "maxLength": 9, + "minLength": 8, + "description": "Número de telefone do usuário" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Data de inclusão do usuário no Admin" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Data de atualização das informações do usuário" + } + } + } + }, + "examples": { + "Usuários": { + "value": [ + { + "id": 1, + "email": "foo@vnda.com.br", + "name": null, + "admin": false, + "renew_password": false, + "role": 1, + "access_token": "706a99d0706a99d0706a99d0706a99d0706a99d0706a99d0706a99d0706a99d0", + "tags": [], + "external_code": null, + "created_at": "2019-11-06T08:50:37.130-03:00", + "updated_at": "2020-03-26T10:40:33.730-03:00" + } + ] + } + }, + "example": [ + { + "id": 1, + "email": "foo@vnda.com.br", + "name": "User 1", + "admin": true, + "renew_password": true, + "role": 1, + "access_token": "706a99d0706a99d070006a99d0706a99d0706a99d0706a99d0706a99d0706a99d0", + "tags": [], + "external_code": null, + "created_at": "2019-11-06T08:50:37.130-03:00", + "updated_at": "2020-03-26T10:40:33.730-03:00" + }, + { + "id": 1, + "email": "test@vnda.com.br", + "name": "User 2", + "admin": false, + "renew_password": false, + "role": 2, + "access_token": "706a99d0706a99dhgs070006a99d0706a99d0706a99d0706a99d0706a99d0706a99d0", + "tags": [], + "external_code": null, + "created_at": "2019-12-06T08:50:37.130-03:00", + "updated_at": "2020-04-26T10:40:33.730-03:00" + } + ] + } + } + }, + "401": { + "description": "Token de acesso inválido" + }, + "404": { + "description": "Domínio da loja inválido", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Usuários" + ], + "description": "Lista os usuários", + "parameters": [ + { + "schema": { + "type": "boolean", + "default": false + }, + "in": "query", + "name": "include_inactive", + "description": "Incluir usuários desativados?" + }, + { + "schema": { + "type": "boolean", + "default": false + }, + "in": "query", + "name": "include_images", + "description": "Incluir todas as imagens dos produtos?" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "external_code", + "description": "Exibe somente os usuários com o código externo indicado" + }, + { + "schema": { + "type": "string", + "enum": [ + "Agente", + "Gestor", + "Local" + ] + }, + "in": "query", + "name": "role_name", + "description": "Exibe somente os usuários com a função indicada" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "term", + "description": "Filtra usuários que contenham o valor indicado no nome, telefone, email ou código externo" + } + ] + }, + "post": { + "summary": "Cria um usuário", + "operationId": "post-api-v2-users", + "responses": { + "200": { + "description": "Quando o usuário é criado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User.v1" + } + } + } + }, + "201": { + "description": "Usuário criado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Usuários" + ], + "description": "Cria um usuário", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "name": { + "type": "string" + }, + "role_name": { + "type": "string", + "enum": [ + "Agente", + "Gestor", + "Local" + ] + }, + "password": { + "type": "string" + }, + "password_confirmation": { + "type": "string" + }, + "external_code": { + "type": "string" + }, + "phone_area": { + "type": "string", + "maxLength": 2 + }, + "phone": { + "type": "string", + "maxLength": 9 + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/api/v2/users/tags": { + "get": { + "summary": "Lista as tags dos usuários a partir das funções", + "operationId": "get-api-v2-users-tags", + "responses": { + "200": { + "description": "Quando as tags são retornadas", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "tags": [ + "Usuários" + ], + "description": "Lista os usuários", + "parameters": [ + { + "schema": { + "type": "string", + "enum": [ + "Agente", + "Gestor", + "Local", + "Agente Social Selling" + ] + }, + "in": "query", + "name": "role_names", + "description": "Exibe somente os usuários com a função indicada" + } + ] + } + }, + "/api/v2/carts": { + "get": { + "summary": "Lista os carrinhos", + "operationId": "get-api-v2-carts", + "responses": { + "200": { + "$ref": "#/components/responses/Carts" + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "tags": [ + "Carrinhos" + ], + "description": "Retorna a lista de carrinhos ativos nos últimos 60 dias", + "parameters": [ + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page", + "description": "Número da página" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "per_page", + "description": "Quantidade de produtos por página" + }, + { + "schema": { + "type": "boolean" + }, + "in": "query", + "name": "without_phones", + "description": "Inclui os carrinhos sem telefone (não enviar o campo para não incluir)" + }, + { + "schema": { + "type": "boolean" + }, + "in": "query", + "name": "with_payments", + "description": "Filtra os carrinhos que possuem tentativa de pagamento" + } + ] + }, + "post": { + "summary": "Cria um carrinho", + "operationId": "post-api-v2-carts", + "description": "Permite criar um carrinho", + "tags": [ + "Carrinhos da loja" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "ipv4" + }, + "in": "header", + "name": "X-Browser-Ip", + "description": "Internet Protocol (IP) da máquina de onde é criado o carrinho", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "X-User-Agent", + "description": "Identificador da origem do carrinho na loja (como navegador ou dispositivo)", + "required": true + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart.simple" + } + } + }, + "description": "" + }, + "responses": { + "201": { + "description": "Carrinho criado com sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart.v1" + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + } + } + }, + "/api/v2/carts/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true, + "description": "Pode ser o id ou o token do carrinho" + } + ], + "patch": { + "summary": "Atualiza um carrinho", + "operationId": "patch-api-v2-carts-id", + "responses": { + "204": { + "description": "Quando o carrinho é atualizado com sucesso" + }, + "404": { + "description": "Quando o carrinho não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + }, + "examples": { + "Com um email inválido": { + "value": { + "errors": { + "client": [ + "não é válido" + ] + } + } + } + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "agent": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "client_id": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true + }, + "email": { + "type": "string", + "description": "DEPRECATED: enviar o `client_id`", + "format": "email" + }, + "rebate_token": { + "type": "string" + } + } + } + } + } + }, + "tags": [ + "Carrinhos" + ], + "description": "Permite atualizar os atributos de um carrinho", + "parameters": [] + }, + "delete": { + "summary": "Exclui um carrinho", + "operationId": "delete-api-v2-carts-id", + "responses": { + "204": { + "description": "Quando um carrinho é excluído com sucesso" + }, + "404": { + "description": "Quando um carrinho não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "tags": [ + "Carrinhos" + ], + "description": "Permite excluir um carrinho", + "parameters": [] + }, + "get": { + "summary": "Retorna um carrinho", + "operationId": "get-api-v2-carts-id", + "responses": { + "200": { + "description": "Quando o carrinho é encontrado", + "headers": { + "X-Attempt-Count": { + "schema": { + "type": "integer" + }, + "description": "O número de tentativas de pagamento" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart.v1" + } + } + } + }, + "404": { + "description": "Quando um carrinho não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "tags": [ + "Carrinhos" + ], + "description": "Permite retornar um carrinho", + "parameters": [] + }, + "post": { + "summary": "Cria um carrinho", + "operationId": "post-api-v2-carts-id", + "responses": { + "201": { + "description": "Quando um carrinho é criado com sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + }, + "examples": { + "Sem os parâmetros obrigatórios": { + "value": { + "errors": { + "browser_ip": [ + "não pode ficar em branco" + ], + "user_agent": [ + "não pode ficar em branco" + ] + } + } + } + } + } + } + } + }, + "description": "Permite criar um carrinho", + "tags": [ + "Carrinhos" + ], + "parameters": [ + { + "schema": { + "type": "string", + "format": "ipv4" + }, + "in": "header", + "name": "X-Browser-Ip", + "description": "IP do usuário que está criando o carrinho na loja", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "header", + "name": "X-User-Agent", + "description": "User-Agent do navegador do usuário criando o carrinho na loja", + "required": true + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "agent": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "client_id": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true + }, + "coupon_code": { + "type": "string" + }, + "email": { + "type": "string", + "description": "DEPRECATED: enviar o `client_id`", + "format": "email" + }, + "rebate_token": { + "type": "string" + } + } + } + } + }, + "description": "" + } + } + }, + "/api/v2/carts/{id}/installments": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Retorna as parcelas do total de um carrinho", + "tags": [ + "Carrinhos" + ], + "responses": { + "200": { + "description": "Quando o carrinho é encontrado", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Cart_installment.v1" + } + } + } + } + }, + "404": { + "description": "Quando um carrinho não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "Não encontrado": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "operationId": "get-api-v2-carts-id-installments", + "description": "Permite calcular as parcelas referentes ao total do carrinho", + "parameters": [] + } + }, + "/api/v2/places": { + "get": { + "summary": "Lista os locais", + "tags": [ + "Locais" + ], + "responses": { + "200": { + "description": "Quando os locais são listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Place.v1" + } + } + } + } + } + }, + "operationId": "get-api-v2-places", + "description": "Lista os locais", + "parameters": [ + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "names", + "description": "Filtra os locais for nome" + }, + { + "schema": { + "type": "boolean", + "default": false + }, + "in": "query", + "name": "warehouse", + "description": "Filtra os locais que são/não são warehouse" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "category", + "description": "Filtra os locais que contenham determinada categoria" + }, + { + "schema": { + "type": "string", + "example": "-30.1087957,-51.3172282" + }, + "in": "query", + "name": "coordinates", + "description": "As lojas mais próximas da coordenada informada serão exibidas primeiro" + }, + { + "schema": { + "type": "string", + "pattern": "^[0-9]{8}$" + }, + "in": "query", + "name": "origin_zip_code", + "description": "As lojas mais próximas do CEP informado serão exibidas primeiro" + } + ] + }, + "post": { + "summary": "Cria um local", + "operationId": "post-api-v2-places", + "responses": { + "201": { + "description": "Quando o local é criado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Place.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos" + } + }, + "tags": [ + "Locais" + ], + "description": "Cria um local", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "address_line_1": { + "type": "string", + "maxLength": 80 + }, + "address_line_2": { + "type": "string", + "maxLength": 80 + }, + "city": { + "type": "string", + "maxLength": 80 + }, + "neighborhood": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "home_page": { + "type": "string" + }, + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "email": { + "type": "string" + }, + "first_phone": { + "type": "string" + }, + "second_phone": { + "type": "string" + }, + "mobile_phone": { + "type": "string", + "default": "false" + }, + "only_cash": { + "type": "boolean" + }, + "categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "marker_url": { + "type": "string" + }, + "state": { + "type": "string" + }, + "opening_hours": { + "type": "string" + }, + "warehouse": { + "type": "boolean" + }, + "legal_name": { + "type": "string" + }, + "cnpj": { + "type": "string" + } + }, + "required": [ + "name", + "address_line_1", + "city", + "email" + ] + } + } + } + } + } + }, + "/api/v2/places/{id}": { + "parameters": [ + { + "schema": { + "type": "integer" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Atualiza um local", + "operationId": "patch-api-v2-places-id", + "responses": { + "204": { + "description": "Quando o local é atualizado" + }, + "404": { + "description": "Quando o local não é encontrado" + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Locais" + ], + "description": "Atualiza um local", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "address_line_1": { + "type": "string", + "maxLength": 80 + }, + "address_line_2": { + "type": "string", + "maxLength": 80 + }, + "city": { + "type": "string", + "maxLength": 80 + }, + "neighborhood": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "home_page": { + "type": "string" + }, + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "email": { + "type": "string" + }, + "first_phone": { + "type": "string" + }, + "second_phone": { + "type": "string" + }, + "mobile_phone": { + "type": "string", + "default": "false" + }, + "only_cash": { + "type": "boolean" + }, + "categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "marker_url": { + "type": "string" + }, + "state": { + "type": "string" + }, + "opening_hours": { + "type": "string" + }, + "warehouse": { + "type": "boolean" + }, + "legal_name": { + "type": "string" + }, + "cnpj": { + "type": "string" + } + }, + "required": [ + "name", + "address_line_1", + "city", + "email" + ] + } + } + } + } + }, + "delete": { + "summary": "Remove um local", + "operationId": "delete-api-v2-places-id", + "responses": { + "204": { + "description": "Quando o local é removido" + }, + "404": { + "description": "Quando o local não é encontrado" + } + }, + "tags": [ + "Locais" + ], + "description": "Remove um local" + } + }, + "/api/v2/orders/{code}/packages/{package_code}/invoices": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "code", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "package_code", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Lista as notas fiscais", + "tags": [ + "Notas fiscais" + ], + "responses": { + "200": { + "description": "Quando as notas fiscais são listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Invoice.v1" + } + } + } + } + } + }, + "operationId": "get-api-v2-orders-code-packages-package_code-invoices", + "description": "Lista as notas fiscais" + }, + "post": { + "summary": "Cria uma nota fiscal", + "operationId": "post-api-v2-orders-code-packages-package_code-invoices", + "responses": { + "201": { + "description": "Quando a nota fiscal é criada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invoice.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos" + } + }, + "tags": [ + "Notas fiscais" + ], + "description": "Cria uma nota fiscal", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "number": { + "type": "number" + }, + "series": { + "type": "number" + }, + "issued_at": { + "type": "string", + "format": "date-time" + }, + "key": { + "type": "string" + }, + "volumes": { + "type": "integer" + } + }, + "required": [ + "number" + ] + } + } + } + } + } + }, + "/api/v2/orders/{code}/packages/{package_code}/invoices/{number}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "code", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "package_code", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "number", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Atualiza uma nota fiscal", + "operationId": "patch-api-v2-orders-code-packages-package_code-invoices-number", + "responses": { + "204": { + "description": "Quando a nota fiscal é atualizada" + }, + "404": { + "description": "Quando a nota fiscal não é encontrada" + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Notas fiscais" + ], + "description": "Atualiza uma nota fiscal", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "number": { + "type": "number" + }, + "series": { + "type": "number" + }, + "issued_at": { + "type": "string", + "format": "date-time" + }, + "key": { + "type": "string" + }, + "volumes": { + "type": "integer" + } + }, + "required": [ + "number" + ] + } + } + } + } + }, + "delete": { + "summary": "Remove uma nota fiscal", + "operationId": "delete-api-v2-orders-code-packages-package_code-invoices-number", + "responses": { + "204": { + "description": "Quando a nota fiscal é removida" + }, + "404": { + "description": "Quando a nota fiscal não é encontrada" + } + }, + "tags": [ + "Notas fiscais" + ], + "description": "Remove uma nota fiscal" + } + }, + "/api/v2/users/reset_password": { + "post": { + "summary": "Solicita renovação da senha", + "operationId": "post-api-v2-users-reset_password", + "responses": { + "200": { + "description": "Quando o email foi enviado" + }, + "404": { + "description": "Quando o usuário não existe" + } + }, + "tags": [ + "Usuários" + ], + "description": "Será enviado por email um link para o cadastro da nova senha\nO link tem validade de 24 horas", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email" + } + }, + "required": [ + "email" + ] + } + } + } + } + }, + "patch": { + "summary": "Cadastra a nova senha", + "operationId": "patch-api-v2-users-reset_password", + "responses": { + "200": { + "description": "Quando a senha foi alterada" + }, + "400": { + "description": "Quando o token é inválido ou expirou" + }, + "404": { + "description": "Quando não foi encontrado um usuário com o token informado" + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Usuários" + ], + "description": "Cadastra a nova senha", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "Token pare renovação de senha enviado por email" + }, + "password": { + "type": "string", + "description": "Nova senha para o usuário" + }, + "password_confirmation": { + "type": "string", + "description": "Confirmação da nova senha do usuário" + } + }, + "required": [ + "token", + "password", + "password_confirmation" + ] + } + } + } + } + } + }, + "/api/v2/orders/{code}/shipping_address": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "code", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Endereço de entrega", + "tags": [ + "Pedidos" + ], + "responses": { + "200": { + "description": "Quando o endereço é retornado", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "company_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "documents": { + "type": "object", + "description": "Serão retornados apenas os campos preenchidos", + "properties": { + "cpf": { + "type": "string" + }, + "cnpj": { + "type": "string" + }, + "ie": { + "type": "string" + } + } + }, + "street_name": { + "type": "string" + }, + "street_number": { + "type": "string", + "example": "188A" + }, + "complement": { + "type": "string" + }, + "neighborhood": { + "type": "string" + }, + "first_phone_area": { + "type": "string", + "description": "Somente números", + "example": "11" + }, + "first_phone": { + "type": "string", + "description": "Somente números", + "example": "984453322" + }, + "second_phone_area": { + "type": "string", + "description": "Somente números" + }, + "second_phone": { + "type": "string", + "description": "Somente números" + }, + "reference": { + "type": "string" + }, + "zip": { + "type": "string", + "description": "Somente números", + "example": "90050000" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string", + "example": "RS", + "minLength": 2, + "maxLength": 2 + }, + "recipient_name": { + "type": "string" + } + }, + "required": [ + "first_name", + "last_name", + "email", + "street_name", + "street_number", + "neighborhood", + "first_phone_area", + "first_phone", + "zip", + "city", + "state" + ] + } + } + } + } + }, + "operationId": "get-api-v2-orders-code-shipping_address", + "description": "Retorna o endereço de entrega" + } + }, + "/api/v2/payment_recipients": { + "get": { + "summary": "Lista os recebedores", + "tags": [ + "Recebedores" + ], + "responses": { + "200": { + "description": "Quando os recebedores são listados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Payment_recipient.v1" + } + } + } + } + } + }, + "operationId": "get-api-v2-payment_recipients", + "description": "Lista os recebedores" + }, + "post": { + "summary": "Cria um recebedor", + "operationId": "post-api-v2-payment_recipients", + "responses": { + "200": { + "description": "Quando o recebedor foi criado com sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Payment_recipient.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + }, + "examples": { + "Parâmetro `percentage` em branco": { + "value": { + "errors": { + "percentage": [ + "não pode ficar em branco" + ] + } + } + } + } + } + } + } + }, + "tags": [ + "Recebedores" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tag_id": { + "type": "integer" + }, + "recipient_id": { + "type": "integer" + }, + "percentage": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "active": { + "type": "boolean", + "default": true + }, + "charge_processing_fee": { + "type": "boolean", + "default": false + }, + "liable": { + "type": "boolean", + "default": false + }, + "code": { + "type": "string" + }, + "place_id": { + "type": "integer" + }, + "user_id": { + "type": "integer" + }, + "include_shipping": { + "type": "boolean", + "default": true, + "description": "Indica se o frete deve ser incluído no split do pagamento" + } + }, + "required": [ + "percentage" + ] + } + } + } + }, + "description": "Cria um recebedor" + } + }, + "/api/v2/payment_recipients/{id}": { + "get": { + "summary": "Retorna um recebedor", + "tags": [ + "Recebedores" + ], + "responses": { + "200": { + "description": "Quando o recebedor é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Payment_recipient.v1" + } + } + } + }, + "404": { + "description": "Quando o recebedor não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "operationId": "get-api-v2-payment_recipients-id", + "description": "Retorna um recebedor" + }, + "patch": { + "summary": "Atualiza um recebedor", + "operationId": "patch-api-v2-payment_recipients-id", + "responses": { + "204": { + "description": "Quando o recebedor é atualizado" + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Recebedores" + ], + "description": "Atualiza um recebedor", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tag_id": { + "type": "integer" + }, + "recipient_id": { + "type": "integer" + }, + "percentage": { + "type": "number", + "maximum": 100 + }, + "active": { + "type": "boolean", + "default": true + }, + "charge_processing_fee": { + "type": "boolean", + "default": false + }, + "liable": { + "type": "boolean", + "default": false + }, + "code": { + "type": "string" + }, + "place_id": { + "type": "integer" + }, + "user_id": { + "type": "integer" + }, + "include_shipping": { + "type": "boolean", + "default": true, + "description": "Indica se o frete deve ser incluído no split do pagamento" + } + } + } + } + } + } + }, + "delete": { + "summary": "Remove um recebedor", + "operationId": "delete-api-v2-payment_recipients-id", + "responses": { + "204": { + "description": "Quando o recebedor é removido" + }, + "404": { + "description": "Quando o recebedor não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "description": "Remove um recebedor", + "tags": [ + "Recebedores" + ] + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ] + }, + "/api/v2/users/{user_id}/payables": { + "get": { + "summary": "Lista os recebíveis do usuário", + "responses": { + "200": { + "description": "Retorna a lista de recebíveis do usuário", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Payables.v1" + } + } + } + } + }, + "404": { + "description": "Quando o usuário não está cadastrado como recebedor", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "description": "Permite a listagem de recebíveis do usuário", + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "user_id", + "in": "path", + "required": true + } + ], + "operationId": "get-api-v2-users-user_id-payables", + "tags": [ + "Usuários" + ] + } + }, + "/api/v2/audience_members": { + "get": { + "summary": "Retorna os membros da audiência", + "responses": { + "200": { + "description": "Quando os membros da audiência são listados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Audience_member.v1" + } + } + } + } + } + }, + "operationId": "get-api-v2-audience_members", + "description": "Lista os membros da audiência", + "tags": [ + "Público" + ] + }, + "post": { + "summary": "Cria uma membro da audiência", + "operationId": "post-api-v2-audience_members", + "responses": { + "201": { + "description": "Quando o membro da audiência é criado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Audience_member.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "description": "Permite criar um membro da audiência", + "tags": [ + "Público" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string" + }, + "phone_area": { + "type": "string", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "email" + ] + } + } + }, + "description": "" + } + }, + "parameters": [] + }, + "/api/v2/audience_members/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "delete": { + "summary": "Remove um membro da audiência", + "operationId": "delete-api-v2-audience-members-id", + "responses": { + "204": { + "description": "Quando o membro da audiência é removido" + }, + "404": { + "description": "Quando a audiência não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Público" + ], + "description": "Permite remover um membro da audiência" + }, + "patch": { + "summary": "Altera um membro da audiência", + "operationId": "patch-api-v2-audience-members-id", + "responses": { + "204": { + "description": "Quando o membro da audiência é alterado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Audience_member.v1" + } + } + } + }, + "404": { + "description": "Quando o membro da audiência não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "description": "Permite alterar um membro da audiência", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "phone_area": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "tags": [ + "Público" + ] + } + }, + "/api/v2/orders/{order_code}/packages/{package_code}/trackings": { + "post": { + "summary": "Adiciona um rastreio", + "operationId": "post-api-v2-orders-order_code-packages-package_code-trackings", + "responses": { + "200": { + "description": "Quando o rastreio é criado", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Código de rastreio do pacote" + }, + "company": { + "type": "string", + "description": "Transportadora" + }, + "url": { + "type": "string", + "description": "URL para rastreio do pacote na transportadora" + } + }, + "required": [ + "code" + ] + }, + "examples": { + "200": { + "value": { + "code": "PL123456789", + "url": "https://examble.com/tracking", + "company": "Correios" + } + } + } + } + } + }, + "404": { + "description": "Quando o pedido ou o pacote não existem", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "tags": [ + "Rastreios" + ], + "description": "Adiciona um rastreio para um pacote de um pedido", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Código de rastreio" + }, + "company": { + "type": "string", + "description": "Transportadora" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Link de rastreamento" + } + }, + "required": [ + "code" + ] + } + } + } + } + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "order_code", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "package_code", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Lista os rastreios", + "operationId": "get-api-v2-orders-order_code-packages-package_code-trackings", + "responses": { + "200": { + "description": "Quando os rastreios são listados", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "tracking_code": { + "type": "string", + "description": "Código de rastreio do pacote" + }, + "tracked_at": { + "type": "string", + "format": "date-time", + "description": "Data e horário da última atualização do código de rastreio do pacote" + }, + "url": { + "type": "string", + "format": "uri", + "description": "URL para rastreio do pedido com a transportadora" + }, + "company": { + "type": "string", + "description": "Transportadora do pacote" + } + }, + "required": [ + "tracking_code" + ] + }, + "examples": { + "Rastreio": { + "value": { + "id": 15, + "tracking_code": "codigo-rastreio", + "tracked_at": "2022-12-23T15:20:18.893-03:00", + "url": "rastreiocorreios.com.br", + "company": "Correios" + } + } + } + } + } + }, + "404": { + "description": "Quando o pedido ou o pacote não existem", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "tags": [ + "Rastreios" + ], + "description": "Lista os rastreios de um pacote de um pedido" + } + }, + "/api/v2/orders/{order_code}/packages/{package_code}/trackings/{id}": { + "delete": { + "summary": "Remove um rastreio", + "operationId": "delete-api-v2-orders-order_code-packages-package_code-trackings-id", + "responses": { + "204": { + "description": "Quando o rastreio é removido" + }, + "404": { + "description": "Quando o pedido ou o pacote não existem", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Rastreios" + ], + "description": "Remove um rastreio" + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "order_code", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "package_code", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ] + }, + "/api/v2/carts/{cart_id}/items": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "cart_id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Lista os itens de um carrinho", + "operationId": "get-api-v2-carts-cart_id-items", + "responses": { + "200": { + "description": "Quando os itens do carrinho são listados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Cart_item.v1" + } + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "description": "Lista os itens de um carrinho", + "tags": [ + "Itens do carrinho" + ] + }, + "post": { + "summary": "Cria um item do carrinho", + "operationId": "post-api-v2-carts-cart_id-items", + "responses": { + "201": { + "description": "Quando um item do carrinho é criado com sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart_item.v1" + } + } + } + }, + "400": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + }, + "examples": { + "Sem os parâmetros obrigatórios": { + "value": { + "errors": { + "sku": [ + "precisa ser informado" + ], + "quantity": [ + "não pode ficar em branco" + ] + } + } + } + } + } + } + }, + "404": { + "description": "Quando um carrinho ou variante não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "Registro não encontrado": { + "value": { + "error": "not found" + } + } + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "description": "Permite criar um item do carrinho", + "tags": [ + "Itens do carrinho" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "quantity": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true + }, + "extra": { + "type": "object" + }, + "place_id": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true + }, + "store_coupon_code": { + "type": "string" + }, + "customizations": { + "type": "array" + } + }, + "required": [ + "sku", + "quantity" + ], + "$ref": "#/components/schemas/Product.v0" + } + } + }, + "description": "Cria um item do carrinho" + } + } + }, + "/api/v2/carts/{cart_id}/items/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "cart_id", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Atualiza um item do carrinho", + "operationId": "patch-api-v2-carts-cart_id-items-id", + "responses": { + "204": { + "description": "Quando o item do carrinho é alterado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Audience_member.v1" + } + } + } + }, + "404": { + "description": "Quando o item do carrinho ou o carrinho não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "description": "Atualiza um item do carrinho", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "quantity": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true + }, + "place_id": { + "type": "integer", + "minimum": 0, + "exclusiveMinimum": true + }, + "extra": { + "type": "object" + }, + "store_coupon_code": { + "type": "string" + } + } + } + } + } + }, + "tags": [ + "Itens do carrinho" + ] + }, + "delete": { + "summary": "Remove um item do carrinho", + "operationId": "delete-api-v2-carts-cart_id-items-id", + "responses": { + "204": { + "description": "Quando o item do carrinho é removido" + }, + "404": { + "description": "Quando o item do carrinho ou o carrinho não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Itens do carrinho" + ], + "description": "Remove um item do carrinho" + } + }, + "/api/v2/carts/{cart_id}/items/bulk": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "cart_id", + "in": "path", + "required": true + } + ], + "post": { + "summary": "Adiciona itens ao carrinho", + "operationId": "post-api-v2-carts-cart_id-items-bulk", + "responses": { + "201": { + "description": "Quando os itens são adicionados com sucesso", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Cart_item.v1" + } + } + } + } + }, + "400": { + "description": "Quando os parâmetros são inválidos", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "errors": { + "type": "object", + "properties": { + "items": { + "type": "object", + "properties": { + "0": { + "type": "object", + "properties": { + "quantity": { + "type": "array", + "items": { + "type": "string" + } + }, + "sku": { + "type": "array", + "items": { + "type": "string" + } + }, + "place_id": { + "type": "array", + "items": { + "type": "string" + } + }, + "": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "examples": { + "Erros de validação": { + "value": { + "errors": { + "items": { + "0": { + "sku": [ + "deve ser preenchido" + ] + }, + "1": { + "quantity": [ + "deve ser maior que 0" + ] + }, + "2": { + "place_id": [ + "deve ser maior que 0" + ] + }, + "3": { + "extra": [ + "precisa ser um hash" + ], + "quantity": [ + "precisa ser um inteiro" + ] + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Quando um carrinho ou variante não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "Registro não encontrado": { + "value": { + "error": "not found" + } + } + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "tags": [ + "Carrinhos" + ], + "description": "Permite adicionar itens em bulk ao carrinho", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "items": { + "type": "array", + "description": "Itens do carrinho", + "items": { + "type": "object", + "properties": { + "sku": { + "type": "string", + "description": "Código SKU da variante do produto" + }, + "quantity": { + "type": "integer", + "description": "Unidades do produto" + }, + "customizations": { + "type": "array", + "description": "[Personalização](http://ajuda.vnda.com.br/pt-BR/articles/1763398-funcionalidades-produtos-personalizados) do produto", + "items": { + "properties": { + "Customization": { + "type": "string", + "description": "Adicione a customização de acordo com a [personalização](http://ajuda.vnda.com.br/pt-BR/articles/1763398-funcionalidades-produtos-personalizados) incluídas no Admin da loja. \nSe por exemplo a customização do produto é a cor, o parâmetro para a requisição deve ser `Color` ao invés de `CUstomization`. \nSaiba mais sobre como utilizar esse parâmetro pelo exemplo de requsição localizado na seção de **Request Example** (ao lado do código da requisição)." + } + } + } + } + }, + "required": [ + "sku", + "quantity" + ] + } + } + }, + "example": { + "items": [ + { + "sku": "teste", + "quantity": 1, + "customizations": [ + { + "Color": "Black" + } + ] + }, + { + "sku": "variante.sku2", + "quantity": 10, + "customizations": [ + { + "Color": "Red" + } + ] + } + ] + } + } + } + } + } + } + }, + "/api/v2/discounts": { + "post": { + "summary": "Cria uma promoção", + "operationId": "post-api-v2-discounts", + "responses": { + "201": { + "description": "Quando a promoção é criada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Discount.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Promoções" + ], + "description": "Cria uma promoção", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "start_at": { + "type": "string", + "format": "date-time" + }, + "end_at": { + "type": "string", + "format": "date-time" + }, + "valid_to": { + "type": "string", + "enum": [ + "store", + "cart" + ] + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean", + "default": true + }, + "email": { + "type": "string", + "format": "email" + }, + "cpf": { + "type": "string", + "pattern": "[0-9]{11}" + }, + "tags": { + "type": "string" + } + }, + "required": [ + "name", + "start_at" + ] + } + } + } + } + } + }, + "/api/v2/discounts/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Retorna uma promoção", + "tags": [ + "Promoções" + ], + "responses": { + "200": { + "description": "Quando a promoção é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Discount.v1" + } + } + } + }, + "404": { + "description": "Quando a promoção não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-discounts-id", + "description": "Retorna uma promoção" + }, + "patch": { + "summary": "Altera uma promoção", + "tags": [ + "Promoções" + ], + "responses": { + "204": { + "description": "Quando a promoção é alterada" + }, + "404": { + "description": "Quando a promoção não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "operationId": "patch-api-v2-discounts-id", + "description": "Altera uma promoção", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "start_at": { + "type": "string", + "format": "date-time" + }, + "end_at": { + "type": "string", + "format": "date-time" + }, + "valid_to": { + "type": "string", + "enum": [ + "store", + "cart" + ] + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean", + "default": true + }, + "email": { + "type": "string", + "format": "email" + }, + "cpf": { + "type": "string", + "pattern": "[0-9]{11}" + }, + "tags": { + "type": "string" + } + }, + "required": [ + "name", + "start_at" + ] + } + } + } + } + }, + "delete": { + "summary": "Remove uma promoção", + "operationId": "delete-api-v2-discounts-id", + "responses": { + "204": { + "description": "Quando a promoção é removida" + }, + "400": { + "description": "Quando a promoção não pode ser removida", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "examples": { + "with-coupons": { + "value": { + "error": "Essa promoção não pode ser excluída pois possui cupons utilizados" + } + } + } + } + } + }, + "404": { + "description": "Quando a promoção não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Promoções" + ], + "description": "Remove uma promoção" + } + }, + "/api/v2/discounts/{discount_id}/rules": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "discount_id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Lista as regras", + "tags": [ + "Regras de desconto" + ], + "responses": { + "200": { + "description": "Quando as regras de desconto são listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Discount_rule.v1" + } + } + } + } + } + }, + "description": "Lista as regras de desconto de uma promoção", + "operationId": "get-api-v2-discounts-discount_id-rules" + }, + "post": { + "summary": "Cria uma regra", + "operationId": "post-api-v2-discounts-discount_id-rules", + "responses": { + "200": { + "description": "Quando a regra de desconto é criada", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "amount": { + "type": "number" + }, + "apply_to": { + "type": "string", + "enum": [ + "product", + "tag", + "subtotal", + "total", + "shipping" + ] + }, + "min_quantity": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "channel": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Regras de desconto" + ], + "description": "Cria uma regra de desconto", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "apply_to": { + "type": "string", + "enum": [ + "product", + "tag", + "subtotal", + "total", + "shipping" + ] + }, + "amount_type": { + "type": "string", + "enum": [ + "R$", + "%" + ] + }, + "amount": { + "type": "number", + "minimum": 0 + }, + "product_id": { + "type": "integer" + }, + "tag_name": { + "type": "string" + }, + "min_quantity": { + "type": "integer" + }, + "shipping_method": { + "type": "string" + }, + "min_subtotal": { + "type": "number", + "minimum": 0 + }, + "gift": { + "type": "boolean", + "default": false + }, + "combinated_product_id": { + "type": "integer" + }, + "client_tag": { + "type": "string" + }, + "shipping_rule": { + "type": "string" + }, + "gift_quantity": { + "type": "integer", + "minimum": 1 + }, + "agent_tag": { + "type": "string" + }, + "regions": { + "type": "array", + "items": { + "type": "string" + } + }, + "channel": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/api/v2/discounts/{discount_id}/rules/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "discount_id", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "delete": { + "summary": "Remove uma regra", + "operationId": "delete-api-v2-discounts-discount_id-rules-id", + "responses": { + "200": { + "description": "Quando a regra de desconto é removida" + } + }, + "tags": [ + "Regras de desconto" + ], + "description": "Remove uma regra de desconto" + }, + "patch": { + "summary": "Altera uma regra", + "operationId": "patch-api-v2-discounts-discount_id-rules-id", + "responses": { + "204": { + "description": "Quandoa regra de desconto é alterada" + }, + "404": { + "description": "Quando a regra de desconto não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "description": "Altera uma regra de desconto", + "tags": [ + "Regras de desconto" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "apply_to": { + "type": "string", + "enum": [ + "product", + "tag", + "subtotal", + "total", + "shipping" + ] + }, + "amount_type": { + "type": "string", + "enum": [ + "R$", + "%" + ] + }, + "amount": { + "type": "number", + "minimum": 0 + }, + "product_id": { + "type": "integer" + }, + "tag_id": { + "type": "integer" + }, + "min_quantity": { + "type": "integer" + }, + "shipping_method": { + "type": "string" + }, + "min_subtotal": { + "type": "number", + "minimum": 0 + }, + "gift": { + "type": "boolean", + "default": false + }, + "combinated_product_id": { + "type": "integer" + }, + "client_tag": { + "type": "string" + }, + "shipping_rule": { + "type": "string" + }, + "gift_quantity": { + "type": "integer", + "minimum": 1 + }, + "agent_tag": { + "type": "string" + }, + "regions": { + "type": "array", + "items": { + "type": "string" + } + }, + "channel": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "/api/v2/discounts/{discount_id}/coupons": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "discount_id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Lista os cupons", + "tags": [ + "Cupons de desconto" + ], + "responses": { + "200": { + "description": "Quando os cupons são retornados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Coupon.v1" + } + } + } + } + } + }, + "operationId": "get-api-v2-discounts-discount_id-coupons", + "description": "Permite listar os cupons de desconto de uma promoção", + "parameters": [ + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "uses_per_code", + "description": "Filtra os cupons pelo campo uses_per_code" + } + ] + }, + "post": { + "summary": "Cria um cupom", + "operationId": "post-api-v2-discounts-discount_id-coupons", + "responses": { + "201": { + "description": "Quando o cupom é criado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Coupon.v1" + } + } + } + }, + "404": { + "description": "Quando o desconto não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Cupons de desconto" + ], + "description": "Cria um cupom de desconto", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string" + }, + "uses_per_code": { + "type": "integer" + }, + "uses_per_user": { + "type": "integer" + }, + "referrer_email": { + "type": "string", + "format": "email" + }, + "quantity": { + "type": "integer" + }, + "user_id": { + "type": "integer" + } + } + } + } + } + } + } + }, + "/api/v2/discounts/{discount_id}/coupons{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "discount_id", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Atualiza um cupom", + "operationId": "patch-api-v2-discounts-discount_id-coupons-coupons_id", + "responses": { + "204": { + "description": "Quando o cupom é atualizado" + }, + "404": { + "description": "Quando o coupom não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Cupons de desconto" + ], + "description": "Atualiza um cupom de desconto", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uses_per_code": { + "type": "integer", + "description": "Caso deseje um uso ilimitado do cupom, o valor desse campo deverá ser 0" + }, + "uses_per_user": { + "type": "integer", + "description": "Caso deseje um uso ilimitado do cupom, o valor desse campo deverá ser 0" + } + } + } + } + } + } + }, + "delete": { + "summary": "Remove um cupom", + "operationId": "delete-api-v2-discounts-discount_id-coupons-id", + "responses": { + "204": { + "description": "Quando o cupom é removido" + }, + "404": { + "description": "Quando o cupom não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando o cupom estiver utilizado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Cupons de desconto" + ], + "description": "Remove um cupom de desconto" + } + }, + "/api/v2/products": { + "get": { + "summary": "Lista os produtos", + "tags": [ + "Produtos" + ], + "responses": { + "200": { + "description": "Quando os produtos são listados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product.v1" + } + }, + "examples": { + "example-1": { + "value": [ + { + "id": 0, + "active": true, + "available": true, + "category_tags": [ + { + "tag_type": "string", + "name": "string", + "title": "string" + } + ], + "description": "string", + "discount_id": 0, + "html_description": "string", + "image_url": "string", + "installments": [ + 0 + ], + "min_quantity": "string", + "name": "string", + "on_sale": true, + "plain_description": "string", + "price": 0, + "rating": { + "rating": 0, + "votes": 0 + }, + "reference": "string", + "sale_price": 0, + "slug": "string", + "tag_names": [ + "string" + ], + "updated_at": "string", + "url": "string", + "variants": [ + { + "{id}": { + "available": true, + "available_quantity": 0, + "custom_attributes": {}, + "handling_days": 0, + "height": 0, + "id": 1, + "image_url": "string", + "installments": [ + 0 + ], + "inventories": [ + { + "created_at": "2019-08-24T14:15:22Z", + "id": 0, + "name": null, + "place_id": 0, + "price": 0, + "quantity": 0, + "quantity_sold": 0, + "sale_price": 0, + "slug": "string", + "updated_at": "2019-08-24T14:15:22Z", + "variant_id": 0 + } + ], + "length": 0, + "main": true, + "min_quantity": 0, + "name": "string", + "norder": 0, + "price": 0, + "product_id": 0, + "properties": { + "property1": { + "defining": true, + "name": "string", + "value": "string" + }, + "property2": { + "defining": true, + "name": "string", + "value": "string" + }, + "property3": { + "defining": true, + "name": "string", + "value": "string" + } + }, + "quantity": 0, + "quantity_sold": 0, + "sale_price": 0, + "sku": "string", + "slug": "string", + "stock": 0, + "updated_at": "2019-08-24T14:15:22Z", + "weight": 0, + "width": 0 + } + } + ], + "discount_rule": null, + "images": [ + { + "id": 0, + "url": "string", + "updated_at": "2019-08-24T14:15:22Z", + "variant_ids": [ + 0 + ] + } + ] + } + ] + } + } + } + } + }, + "404": { + "description": "Domínio de loja não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-products", + "description": "Lista os produtos", + "parameters": [ + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "limit", + "description": "Delimita a quantidade de itens retornados" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page", + "description": "Número da página" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "per_page", + "description": "Quantidade de produtos por página" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "reference", + "description": "Filtra pela referência" + }, + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "ids", + "description": "Filtra pelo ID dos produtos " + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "tag", + "description": "Filtra produtos que coném a tag" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "updated_after", + "description": "Filtra produtos alterados depois da data" + }, + { + "schema": { + "type": "string", + "enum": [ + "newest" + ] + }, + "in": "query", + "name": "sort", + "description": "Exibe os produtos cadastrados recentemente primeiro" + }, + { + "schema": { + "type": "boolean" + }, + "in": "query", + "name": "include_inactive", + "description": "Inclui os produtos inativos na listagem" + }, + { + "schema": { + "type": "boolean" + }, + "in": "query", + "name": "include_images", + "description": "Inclui na requisição se deseja que venham todas as imagens do produto" + } + ] + }, + "post": { + "summary": "Cria um produto", + "operationId": "post-api-v2-products", + "responses": { + "201": { + "description": "Quando o produto é criado", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "active": { + "type": "string", + "description": "Indica se o produto está ativo (`true`) ou inativo (`false`)", + "default": true + }, + "reference": { + "type": "string", + "description": "Código de Referência do produto" + }, + "name": { + "type": "string", + "description": "Nome do produto" + }, + "description": { + "type": "string", + "description": "Descrição do produto" + }, + "tag_list": { + "type": "array", + "items": { + "type": "string" + }, + "example": "tag1, tag2", + "description": "Lista de tags associadas ao produto" + }, + "slug": { + "type": "string" + }, + "url": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "product_type": { + "description": "Tipo de produto, entre:\n - `sample`: amostra\n - `subscription`: assinatura\n - `product`: produto em geral", + "type": "string", + "enum": [ + "product", + "sample", + "subscription" + ], + "default": "product" + } + }, + "required": [ + "reference", + "name" + ] + } + } + } + }, + "404": { + "description": "Domínio de loja não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Produtos" + ], + "description": "Cria um produto", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "reference": { + "type": "string" + }, + "tag_list": { + "type": "string", + "example": "tag1, tag2" + } + }, + "required": [ + "name", + "reference" + ], + "$ref": "#/components/schemas/SimpleProduct" + } + } + } + } + } + }, + "/api/v2/products/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Retorna um produto", + "tags": [ + "Produtos" + ], + "responses": { + "200": { + "description": "Quando o produto é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product.v1" + }, + "examples": { + "example-1": { + "value": { + "id": 0, + "active": true, + "available": true, + "category_tags": [ + { + "tag_type": "string", + "name": "string", + "title": "string" + } + ], + "description": "string", + "discount_id": 0, + "html_description": "string", + "image_url": "string", + "installments": [ + 0 + ], + "min_quantity": "string", + "name": "string", + "on_sale": true, + "plain_description": "string", + "price": 0, + "rating": { + "rating": 0, + "votes": 0 + }, + "reference": "string", + "sale_price": 0, + "slug": "string", + "tag_names": [ + "string" + ], + "updated_at": "string", + "url": "string", + "variants": [ + { + "{id}": { + "available": true, + "available_quantity": 0, + "custom_attributes": {}, + "handling_days": 0, + "height": 0, + "id": 1, + "image_url": "string", + "installments": [ + 0 + ], + "inventories": [ + { + "created_at": "2019-08-24T14:15:22Z", + "id": 0, + "name": null, + "place_id": 0, + "price": 0, + "quantity": 0, + "quantity_sold": 0, + "sale_price": 0, + "slug": "string", + "updated_at": "2019-08-24T14:15:22Z", + "variant_id": 0, + "place_name": "string" + } + ], + "length": 0, + "main": true, + "min_quantity": 0, + "name": "string", + "norder": 0, + "price": 0, + "product_id": 0, + "properties": { + "property1": { + "defining": true, + "name": "string", + "value": "string" + }, + "property2": { + "defining": true, + "name": "string", + "value": "string" + }, + "property3": { + "defining": true, + "name": "string", + "value": "string" + } + }, + "quantity": 0, + "quantity_sold": 0, + "sale_price": 0, + "sku": "string", + "slug": "string", + "stock": 0, + "updated_at": "2019-08-24T14:15:22Z", + "weight": 0, + "width": 0 + } + } + ], + "discount_rule": null, + "images": [ + { + "id": 0, + "url": "string", + "updated_at": "2019-08-24T14:15:22Z", + "variant_ids": [ + 0 + ] + } + ] + } + } + } + } + } + }, + "404": { + "description": "Quando o produto não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product.v1" + } + } + } + } + }, + "operationId": "get-api-v2-products-id", + "description": "Retorna um produto", + "parameters": [ + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "coupon_codes", + "description": "Lista de cupons para calcular o desconto do produto" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include_inventory_place", + "description": "Se \"true\", inclui o nome do local nos inventários das variantes" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include_images", + "description": "Se \"true\", inclui todas as imagens do produto" + } + ] + }, + "patch": { + "summary": "Atualiza um produto", + "operationId": "patch-api-v2-products-id", + "responses": { + "204": { + "description": "Quando o produto é atualizado" + }, + "404": { + "description": "Quando o produto não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviado são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Produtos" + ], + "description": "Atualiza um produto", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "reference": { + "type": "string" + }, + "tag_list": { + "type": "string", + "example": "tag1, tag2" + } + }, + "required": [ + "name", + "reference" + ] + } + } + } + } + }, + "delete": { + "summary": "Remove um produto", + "operationId": "delete-api-v2-products-id", + "responses": { + "204": { + "description": "Quando o produto é removido" + }, + "404": { + "description": "Quando o produto não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Produtos" + ], + "description": "Remove um produto" + } + }, + "/api/v2/products/reference/{reference}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "reference", + "in": "path", + "required": true, + "description": "Referência do produto" + } + ], + "patch": { + "summary": "Atualiza um produto pela referência", + "operationId": "patch-api-v2-products-reference-reference", + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "description": "Permite atualizar um produto pela referência", + "requestBody": { + "$ref": "#/components/requestBodies/Product", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleProduct", + "type": "object", + "properties": { + "reference": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "active": { + "type": "boolean", + "default": true + }, + "product_type": { + "type": "string", + "enum": [ + "product", + "sample", + "subscription" + ], + "default": "product" + } + }, + "required": [ + "reference", + "name" + ] + } + } + }, + "description": "" + }, + "tags": [ + "Produtos" + ] + } + }, + "/api/v2/products/{id}/rate": { + "post": { + "summary": "Avalia um produto", + "tags": [ + "Produtos" + ], + "responses": { + "200": { + "description": "Quando a avaliação é recebida", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "rating": { + "type": "string" + }, + "votes": { + "type": "string" + } + } + }, + "examples": { + "example-1": { + "value": { + "rating": "0.9", + "votes": "2" + } + } + } + } + } + }, + "400": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "examples": { + "example-1": { + "value": { + "error": "invalid rate value" + } + } + } + } + } + }, + "404": { + "description": "Quando o produto não tem variantes", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "examples": { + "example-1": { + "value": { + "error": "product without variants" + } + } + } + } + } + } + }, + "operationId": "get-api-v2-products-id-rate", + "description": "Recebe uma avaliação e recalcula a pontuação atual", + "parameters": [ + { + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 5 + }, + "in": "query", + "name": "rate", + "description": "Avaliação" + } + ] + }, + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ] + }, + "/api/v2/products/search": { + "get": { + "summary": "Busca os produtos", + "tags": [ + "Produtos" + ], + "responses": { + "200": { + "description": "Quando os produtos são encontrados", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProductSearch" + } + }, + "aggregations": { + "type": "object", + "properties": { + "min_price": { + "type": "number" + }, + "max_price": { + "type": "number" + }, + "types": { + "type": "object" + }, + "properties": { + "type": "object", + "properties": { + "property1": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "count": { + "type": "number" + } + } + } + }, + "property2": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "count": { + "type": "number" + } + } + } + }, + "property3": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "count": { + "type": "number" + } + } + } + } + } + } + } + } + } + }, + "examples": { + "example-1": { + "value": { + "results": [ + { + "id": 0, + "active": true, + "available": true, + "subscription": true, + "slug": "string", + "reference": "string", + "reference_lowercase": "string", + "name": "string", + "description": "string", + "image_url": "string", + "url": "string", + "tags": [ + { + "name": "string", + "title": "string", + "subtitle": "string", + "description": "string", + "importance": 0, + "type": "string", + "image_url": "string" + } + ], + "price": 0, + "on_sale": true, + "sale_price": 0, + "intl_price": 0, + "discount_id": 0, + "discount_rule": { + "type": "fixed", + "amount": 0 + }, + "discount": { + "name": "string", + "description": "string", + "facebook": true, + "valid_to": "string" + }, + "images": [ + { + "sku": "string", + "url": "string" + } + ], + "variants": [ + { + "id": 1, + "sku": "string", + "sku_lowercase": "string", + "name": "string", + "full_name": "string", + "main": true, + "available": true, + "image_url": "string", + "price": 0, + "sale_price": 0, + "intl_price": 0, + "installments": [ + { + "number": 1, + "price": 10, + "interest": false, + "interest_rate": 0, + "total": 10 + } + ], + "stock": 0, + "quantity": 0, + "quantity_sold": 0, + "min_quantity": 0, + "available_quantity": 0, + "custom_attributes": {}, + "properties": { + "property1": { + "defining": true, + "name": "string", + "value": "string" + }, + "property2": { + "defining": true, + "name": "string", + "value": "string" + }, + "property3": { + "defining": true, + "name": "string", + "value": "string" + } + }, + "inventories": [ + { + "name": null, + "slug": "string", + "available": true, + "price": 0, + "sale_price": 0, + "quantity": 0, + "quantity_sold": 0, + "place": { + "id": 0, + "name": "string" + } + } + ], + "handling_days": 0, + "barcode": "string", + "weight": 0, + "width": 0, + "height": 0, + "length": 0 + } + ], + "installments": [ + { + "number": 1, + "price": 10, + "interest": false, + "interest_rate": 0, + "total": 10 + } + ], + "created_at": "2019-08-24T14:15:22Z", + "updated_at": "2019-08-24T14:15:22Z" + } + ], + "aggregations": { + "min_price": 0, + "max_price": 0, + "types": { + "tag_0": [ + { + "name": "string", + "title": "string", + "count": 0 + }, + { + "name": "string", + "title": "string", + "count": 0 + } + ], + "tag_1": [ + { + "name": "string", + "title": "string", + "count": 0 + } + ] + }, + "properties": { + "property1": [ + { + "value": "string", + "count": 0 + } + ], + "property2": [ + { + "value": "string", + "count": 0 + } + ], + "property3": [ + { + "value": "string", + "count": 0 + } + ] + } + } + } + } + } + } + } + } + }, + "operationId": "get-api-v2-products-search", + "description": "Busca os produtos de acordo com os parâmetros definidos", + "parameters": [ + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page", + "description": "Número da página" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "per_page", + "description": "Quantidade de produtos por página" + }, + { + "schema": { + "type": "array", + "items": { + "type": "integer" + } + }, + "in": "query", + "name": "ids[]", + "description": "Filtra pelo ID dos produtos" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "term", + "description": "Filtra produtos que contenham o termo" + }, + { + "schema": { + "type": "boolean" + }, + "in": "query", + "name": "wildcard", + "description": "Permite que o filtro 'term' realize filtragem de produtos por termo parcial" + }, + { + "schema": { + "type": "object" + }, + "in": "query", + "name": "type_tags[]", + "description": "Filtra pelo nome da tag dentro de um tipo de tag. Exemplo, type_tags[cor]=verde" + }, + { + "schema": { + "type": "string", + "enum": [ + "and", + "or" + ] + }, + "in": "query", + "name": "type_tags_operator", + "description": "Operador lógico para o filtro de tag" + }, + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "property1_values[]", + "description": "Filtra pelo valor da propriedade 1" + }, + { + "schema": { + "type": "string", + "enum": [ + "and", + "or" + ] + }, + "in": "query", + "name": "property1_operator", + "description": "Operador lógico para o filtro de valor da propriedade 1" + }, + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "property2_values[]", + "description": "Filtra pelo valor da propriedade 2" + }, + { + "schema": { + "type": "string", + "enum": [ + "and", + "or" + ] + }, + "in": "query", + "name": "property2_operator", + "description": "Operador lógico para o filtro de valor da propriedade 2" + }, + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "property3_values[]", + "description": "Filtra pelo valor da propriedade 3" + }, + { + "schema": { + "type": "string", + "enum": [ + "and", + "or" + ] + }, + "in": "query", + "name": "property3_operator", + "description": "Operador lógico para o filtro de valor da propriedade 3" + }, + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "min_price", + "description": "Filtra pelo preço de venda mínimo do produto" + }, + { + "schema": { + "type": "number" + }, + "in": "query", + "name": "max_price", + "description": "Filtra pelo preço de venda máximo do produto" + }, + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "tags[]", + "description": "Filtra pelo nome das tags, independente do tipo" + }, + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "parent_tags", + "description": "Filtra pelo nome das tags, independente do tipo" + }, + { + "schema": { + "type": "boolean" + }, + "in": "query", + "name": "show_only_available", + "description": "Filtra por produtos disponíveis" + }, + { + "schema": { + "type": "string", + "enum": [ + "newest", + "oldest", + "lowest_price", + "highest_price" + ] + }, + "in": "query", + "name": "sort", + "description": "Ordena o resultado da busca de produtos conforme a opção escolhida" + } + ] + } + }, + "/api/v2/variants/{sku}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "sku", + "in": "path", + "required": true, + "description": "SKU da variante" + } + ], + "get": { + "summary": "Retorna uma variante", + "tags": [ + "Variantes" + ], + "responses": { + "201": { + "description": "Quando a variante é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Variant" + }, + "examples": { + "example-1": { + "value": { + "id": 95, + "main": false, + "available": true, + "sku": "SHOP0001", + "name": "Variation", + "slug": "variation", + "min_quantity": 1, + "quantity": 1, + "quantity_sold": 0, + "stock": 1, + "custom_attributes": {}, + "properties": {}, + "updated_at": "2020-10-27T11:54:32.018-03:00", + "price": 10, + "installments": [ + 10 + ], + "available_quantity": 1, + "weight": 0.001, + "width": 1, + "height": 1, + "length": 1, + "handling_days": 0, + "inventories": [], + "sale_price": 10, + "image_url": "//b0.vnda.com.br/x120/shop/2014/07/08/variation.jpg", + "product_id": 6, + "norder": 1 + } + } + } + } + } + }, + "404": { + "description": "Quando a variante não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-variants-sku", + "description": "Retorna uma variante pelo SKU" + }, + "patch": { + "summary": "Atualiza uma variante", + "operationId": "patch-api-v2-variants-sku", + "responses": { + "204": { + "$ref": "#/components/responses/204" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "description": "Permite atualizar uma variante pelo SKU", + "tags": [ + "Variantes" + ], + "requestBody": { + "$ref": "#/components/requestBodies/Variant", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "name": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "main": { + "type": "boolean" + }, + "width": { + "type": "number", + "description": "Largura do produto, em centímetros" + }, + "height": { + "type": "number", + "description": "Altura do produto, em centímetros" + }, + "length": { + "type": "number", + "description": "Comprimento do produito, em centímetros" + }, + "weight": { + "type": "number", + "description": "Massa do produto, em gramas" + }, + "handling_days": { + "type": "integer", + "description": "Dias de manuseio da variante" + }, + "price": { + "type": "number", + "description": "Preço do item" + }, + "custom_attributes": { + "type": "object", + "description": "Customização da variante" + }, + "min_quantity": { + "type": "integer" + }, + "norder": { + "type": "integer" + }, + "property1": { + "$ref": "#/components/schemas/VariantProperty" + }, + "property2": { + "$ref": "#/components/schemas/VariantProperty" + }, + "property3": { + "$ref": "#/components/schemas/VariantProperty" + }, + "barcode": { + "type": "string" + }, + "quantity_sold": { + "type": "integer", + "description": "Quantidade de itens vendidos" + } + }, + "required": [ + "sku", + "quantity", + "price" + ] + } + } + } + } + } + }, + "/api/v2/products/{product_id}/variants/{sku}/images": { + "parameters": [ + { + "$ref": "#/components/parameters/product_id" + }, + { + "$ref": "#/components/parameters/sku" + } + ], + "get": { + "summary": "Lista as imagens da variante com SKU na URL", + "tags": [ + "Variantes" + ], + "responses": { + "200": { + "$ref": "#/components/responses/VariantImages" + } + }, + "operationId": "get-api-v2-products-product_id-variants-sku-images", + "description": "Lista as imagens de uma variante passando o SKU da mesma na URL" + } + }, + "/api/v2/products/{product_id}/variants/images": { + "parameters": [ + { + "$ref": "#/components/parameters/product_id" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "sku", + "description": "SKU da variante" + } + ], + "get": { + "summary": "Lista as imagens da variante com SKU nos parâmetros", + "tags": [ + "Variantes" + ], + "responses": { + "200": { + "$ref": "#/components/responses/VariantImages" + } + }, + "operationId": "get-api-v2-products-product_id-variants-images", + "description": "Lista as imagens de uma variante passando o SKU da mesma nos parâmetros" + } + }, + "/api/v2/variants/{sku}/shipping_methods": { + "get": { + "parameters": [ + { + "$ref": "#/components/parameters/sku" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "quantity", + "allowEmptyValue": false, + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "zip", + "allowEmptyValue": false, + "required": true + } + ], + "summary": "Calcula frete", + "tags": [ + "Variantes" + ], + "operationId": "get-api-v2-variants-variant_sku-shipping_methods", + "responses": { + "200": { + "$ref": "#/components/responses/VariantShippings" + }, + "404": { + "description": "Quando a variante não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "description": "Calcula o frete para uma determinada variante" + } + }, + "/api/v2/tags": { + "get": { + "summary": "Lista as tags", + "tags": [ + "Tags" + ], + "responses": { + "200": { + "description": "Quando as tags são listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Tag.v1" + } + }, + "examples": { + "example-1": { + "value": [ + { + "products_count": 1, + "name": "camiseta", + "image_url": null, + "type": "", + "title": "Camisetas Legais", + "updated_at": "2015-03-10T04:33:08.699-03:00", + "subtitle": "Adulto e infantil", + "description": "

A loja oferece Camisetas para adultos e crianças

\n" + }, + { + "updated_at": "2017-11-05T13:23:50.107-02:00", + "products_count": 0, + "image_url": "//a0.vnda.com.br/loja/2017/03/14/15_49_52_10_Flag.png?1509895430", + "name": "promo-camiseta", + "title": "promo-camiseta", + "subtitle": null, + "description": null, + "type": "flag" + } + ] + } + } + } + } + } + }, + "operationId": "get-api-v2-tags", + "description": "Permite listar as tags", + "parameters": [ + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "limit", + "description": "Indica a quantidade de tags que devem ser listadas (page será ignorado)", + "deprecated": true + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page", + "description": "Número da página" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "per_page", + "description": "Quantidade de resultados por página" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "type", + "description": "Exibe somente as tags com o tipo indicado" + }, + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "types", + "description": "Exibe somente as tags com um dos tipos indicados" + }, + { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "names", + "description": "Exibe somente as tags com um dos nomes indicados" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "images", + "description": "Quando passado qualquer valor filtra as tags que contenham imagens" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "show_in_carts", + "description": "Quando passado qualquer valor filtra as tags marcadas para serem exibidas no carrinho" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "product_id", + "description": "Exibe somente as tags do produto indicado" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "name", + "description": "Texto livre que permite filtrar as tags pelo nome" + }, + { + "schema": { + "type": "string", + "default": "name,asc", + "enum": [ + "name,asc", + "name,desc", + "type,asc", + "type,desc", + "title,asc", + "title,desc", + "products_count,asc", + "products_count,desc" + ] + }, + "in": "query", + "name": "sort", + "description": "String no formato , que determina o campo a ser ordenado e qual a ordem (asc,desc)" + } + ] + }, + "post": { + "summary": "Cria uma tag", + "operationId": "post-api-v2-tags", + "responses": { + "201": { + "description": "Quando a tag é criada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Tag.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetos enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Tags" + ], + "description": "Cria uma tag", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "title": { + "type": "string" + }, + "blurb": { + "type": "string", + "description": "Equivalente ao subtítulo" + }, + "description": { + "type": "string" + }, + "tag_type": { + "type": "string" + }, + "show_in_carts": { + "type": "boolean" + } + }, + "required": [ + "name" + ] + } + } + } + } + } + }, + "/api/v2/tags/types": { + "get": { + "summary": "Lista os tipos de tags", + "tags": [ + "Tags" + ], + "responses": { + "200": { + "description": "Quanto os tipos são listados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, + "operationId": "get-api-v2-tags-types", + "description": "Lista os tipos de tags usados em alguma tag", + "parameters": [ + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page", + "description": "Número da página" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "per_page", + "description": "Quantidade de resultados por página" + } + ] + } + }, + "/api/v2/tags/{name}": { + "parameters": [ + { + "schema": { + "type": "string", + "pattern": "[a-z0-9\\-_]+" + }, + "name": "name", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Retorna uma tag", + "tags": [ + "Tags" + ], + "responses": { + "200": { + "description": "Quando a tag é retornada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Tag.v1" + } + } + } + }, + "404": { + "description": "Quando a tag não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-tags-name", + "description": "Retorna uma tag" + }, + "patch": { + "summary": "Atualiza uma tag", + "operationId": "patch-api-v2-tags-name", + "responses": { + "204": { + "description": "Quando a tag é atualizada" + }, + "404": { + "description": "Quando a tag não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Tags" + ], + "description": "Permite atualizar uma tag" + }, + "delete": { + "summary": "Remove uma tag", + "operationId": "delete-api-v2-tags-name", + "responses": { + "204": { + "description": "Quando a tag é removida" + }, + "404": { + "description": "Quando a tag não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Tags" + ], + "description": "Remove uma tag" + } + }, + "/api/v2/coupon_codes/{code}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "code", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Retorna um cupom", + "tags": [ + "Cupons de desconto" + ], + "responses": { + "200": { + "description": "Quando o cupom é encontrado", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "code": { + "type": "string" + }, + "discount_id": { + "type": "number" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "code", + "discount_id", + "updated_at" + ] + }, + "examples": { + "example-1": { + "value": { + "code": "98008F", + "discount_id": 1, + "id": 1231, + "updated_at": "2020-10-27T19:12:51.858-03:00" + } + } + } + } + } + }, + "404": { + "description": "Quando o cupom não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-coupon_codes-code", + "description": "Retorna os dados de um cupom usando o seu código" + } + }, + "/api/v2/carts/{cart_id}/payment/paypal": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "cart_id", + "in": "path", + "required": true + } + ], + "post": { + "summary": "Cria um pedido no Paypal", + "operationId": "post-api-v2-carts-cart_id-payments-paypal", + "responses": { + "200": { + "description": "Quando o pedido é criado no Paypal", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string" + }, + "id": { + "type": "string" + }, + "links": { + "type": "array", + "items": { + "type": "object", + "properties": { + "href": { + "type": "string" + }, + "rel": { + "type": "string" + }, + "method": { + "type": "string" + } + } + } + } + } + }, + "examples": { + "example-1": { + "value": { + "id": "31G50456P87181405", + "status": "CREATED", + "links": [ + { + "href": "https://api.sandbox.paypal.com/v2/checkout/orders/31G50456P87181405", + "rel": "self", + "method": "GET" + }, + { + "href": "https://www.sandbox.paypal.com/checkoutnow?token=31G50456P87181405", + "rel": "approve", + "method": "GET" + }, + { + "href": "https://api.sandbox.paypal.com/v2/checkout/orders/31G50456P87181405", + "rel": "update", + "method": "PATCH" + }, + { + "href": "https://api.sandbox.paypal.com/v2/checkout/orders/31G50456P87181405/capture", + "rel": "capture", + "method": "POST" + } + ] + } + } + } + } + } + }, + "400": { + "description": "Quando o pedido não é criado", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + }, + "tags": [ + "Pagamentos" + ], + "description": "Cria um pedido no Paypal para que posteriormente possa receber um pagamento" + } + }, + "/api/v2/clients": { + "get": { + "summary": "Lista os clientes", + "tags": [ + "Clientes" + ], + "responses": { + "200": { + "description": "Quando os clientes são encontrados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Client.v1" + } + } + } + } + } + }, + "operationId": "get-api-v2-clients", + "description": "Retorna uma lista de clientes. Caso seja informado o parâmetro \"email\", então apenas o cliente com esse email será retornado", + "parameters": [ + { + "schema": { + "type": "string", + "format": "email" + }, + "in": "query", + "name": "email", + "description": "Retorna somente o cliente com o email informado", + "deprecated": true + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "page", + "description": "Número da página" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "per_page", + "description": "Registros por página" + }, + { + "schema": { + "type": "string", + "format": "date-time" + }, + "in": "query", + "name": "min_updated_at", + "description": "Filtra os clientes pela menor data de atualização" + }, + { + "schema": { + "type": "string", + "format": "date-time" + }, + "in": "query", + "name": "max_updated_at", + "description": "Filtra os clientes pela maior data de atualização" + }, + { + "schema": { + "type": "string", + "format": "date-time" + }, + "in": "query", + "name": "birthday_start", + "description": "Data de inicío da filtragem de clientes pela data de aniversário" + }, + { + "schema": { + "type": "string", + "format": "date-time" + }, + "in": "query", + "name": "birthday_end", + "description": "Data final da filtragem de clientes pela data de aniversário" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "has_phone", + "description": "Filtra os clientes que possuem telefone" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "has_first_name", + "description": "Filtra os clientes que possuem first name" + }, + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "user_id", + "description": "Filtra os clientes por vendedor" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "term", + "description": "Filtra os clientes que possuem o termo em alguns dos campos" + }, + { + "schema": { + "type": "string", + "enum": [ + "name", + "birthdate" + ] + }, + "in": "query", + "name": "sort", + "description": "Ordena o resultado da busca de clientes conforme a opção escolhida" + } + ], + "security": [ + { + "Token": [] + } + ] + }, + "post": { + "summary": "Cria um cliente", + "operationId": "post-api-v2-clients", + "responses": { + "201": { + "description": "Quando o cliente é criado", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "gender": { + "type": "string" + }, + "phone_area": { + "type": "string", + "pattern": "[0-9]+" + }, + "phone": { + "type": "string", + "pattern": "[0-9]+" + }, + "cpf": { + "type": "string", + "pattern": "[0-9]+" + }, + "cnpj": { + "type": "string", + "pattern": "[0-9]+" + }, + "ie": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "lists": { + "type": "array", + "items": { + "type": "string" + } + }, + "facebook_uid": { + "type": "string" + }, + "liked_facebook_page": { + "type": "boolean" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "birthdate": { + "type": "string", + "format": "date" + }, + "recent_address": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "company_name": { + "type": "string" + }, + "street_name": { + "type": "string" + }, + "street_number": { + "type": "string" + }, + "neighborhood": { + "type": "string" + }, + "complement": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "first_phone_area": { + "type": "string" + }, + "first_phone": { + "type": "string" + }, + "second_phone_area": { + "type": "string" + }, + "second_phone": { + "type": "string" + }, + "email": { + "type": "string" + }, + "documents": { + "type": "object", + "properties": { + "cpf": { + "type": "string" + }, + "cnpj": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Clientes" + ], + "description": "Permite criar um cliente", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "birthdate": { + "type": "string", + "format": "date" + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F" + ] + }, + "tags": { + "type": "string", + "description": "separado por vírgula", + "example": "foo,bar" + }, + "lists": { + "type": "array", + "items": { + "type": "string" + } + }, + "password": { + "type": "string" + }, + "password_confirmation": { + "type": "string" + }, + "terms": { + "type": "boolean" + } + } + } + } + } + } + } + }, + "/api/v2/clients/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true, + "description": "" + } + ], + "get": { + "summary": "Retorna um cliente", + "tags": [ + "Clientes" + ], + "responses": { + "200": { + "description": "Quando o cliente é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Client.v1" + } + } + } + }, + "404": { + "description": "Quando o cliente não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-clients-id", + "description": "Permite retornar as informações do cliente\nO auth_token do cliente pode ser informado no lugar do ID na URL" + }, + "patch": { + "summary": "Atualiza um cliente", + "operationId": "patch-api-v2-clients-id", + "responses": { + "204": { + "description": "Quando o cliente é atualizado" + }, + "404": { + "description": "Quando o cliente não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados estão incorretos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Clientes" + ], + "description": "Permite atualizar as informações do cliente", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "birthdate": { + "type": "string", + "format": "date" + }, + "gender": { + "type": "string", + "enum": [ + "M", + "F" + ] + }, + "tags": { + "type": "string", + "description": "separado por vírgula", + "example": "foo,bar" + }, + "lists": { + "type": "array", + "items": { + "type": "string" + } + }, + "password": { + "type": "string" + }, + "password_confirmation": { + "type": "string" + }, + "terms": { + "type": "boolean" + } + } + } + } + } + } + }, + "delete": { + "summary": "Remove um cliente", + "operationId": "delete-api-v2-clients-id", + "responses": { + "204": { + "description": "Quando o cliente é removido" + }, + "404": { + "description": "Quando o cliente não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Clientes" + ], + "description": "Permite remover um cliente" + } + }, + "/api/v2/clients/{id}/orders": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Lista os pedidos", + "tags": [ + "Clientes" + ], + "responses": { + "200": { + "description": "Quando os pedidos são retornados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Order.v1" + } + } + } + } + } + }, + "operationId": "get-api-v2-clients-id-orders", + "description": "Retorna a lista de pedidos do cliente" + } + }, + "/api/v2/clients/{id}/addresses": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Lista os endereços", + "tags": [ + "Clientes" + ], + "responses": { + "200": { + "description": "Quando os endereços são listados", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Address.v1" + } + } + } + } + }, + "operationId": "get-api-v2-clients-id-addresses", + "description": "Lista os endereços do cliente utilizados nos pedidos que foram confirmados", + "parameters": [ + { + "$ref": "#/components/parameters/status" + } + ] + } + }, + "/api/v2/clients/{client_id}/registered_addresses": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "client_id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Lista os endereços cadastrados pelo cliente", + "tags": [ + "Clientes" + ], + "responses": { + "200": { + "description": "Quando os endereços são listados", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientAddress.v1" + } + } + } + } + }, + "operationId": "get-api-v2-clients-id-regitered-addresses", + "description": "Lista os endereços cadastrados pelo cliente" + }, + "post": { + "summary": "Cria um endereço do cliente", + "operationId": "post-api-v2-clients-id-regitered-addresses", + "responses": { + "201": { + "description": "Quando o endereço do cliente é criado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientAddress.v1" + } + } + } + }, + "404": { + "description": "Quando o cliente não existe", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Clientes" + ], + "description": "Permite criar um endereço do cliente", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "street_name": { + "type": "string" + }, + "street_number": { + "type": "string" + }, + "complement": { + "type": "string" + }, + "neighborhood": { + "type": "string" + }, + "label": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "reference": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/v2/clients/{client_id}/registered_addresses/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "client_id", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Atualiza um endereço do cliente", + "operationId": "patch-api-v2-clients-id-regitered-addresses-id", + "responses": { + "200": { + "description": "Quando o endereço do cliente é atualizado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientAddress.v1" + } + } + } + }, + "404": { + "description": "Quando o cliente ou endereço não existe", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Clientes" + ], + "description": "Permite atualizar um endereço do cliente", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "street_name": { + "type": "string" + }, + "street_number": { + "type": "string" + }, + "complement": { + "type": "string" + }, + "neighborhood": { + "type": "string" + }, + "label": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "reference": { + "type": "string" + } + } + } + } + } + } + }, + "delete": { + "summary": "Deleta o endereço cadastrados pelo cliente", + "tags": [ + "Clientes" + ], + "responses": { + "204": { + "description": "Quando o endereço é deletado" + }, + "404": { + "description": "Quando o endereço não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "delete-api-v2-clients-id-regitered-addresses-id", + "description": "Delete o endereço cadastrado pelo cliente" + } + }, + "/api/v2/clients/recover_password": { + "parameters": [], + "post": { + "summary": "Reseta a senha", + "operationId": "post-api-v2-clients-recover_password", + "responses": { + "200": { + "description": "Quando a senha for criada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Client.v1" + } + } + } + }, + "404": { + "description": "Quando um cliente não é encontrado com o email informado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Clientes" + ], + "description": "Cria uma senha para o cliente e envia por email", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "email", + "description": "Email do cliente", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "no_send", + "description": "Preencher para pular o envio do email de senha para o cliente" + } + ] + } + }, + "/api/v2/clients/{id}/credits": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Saldo de créditos", + "tags": [ + "Clientes" + ], + "responses": { + "200": { + "description": "Quando o saldo é retornado", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "balance": { + "type": "number" + } + } + } + } + } + } + }, + "operationId": "get-api-v2-clients-client_id-credits", + "description": "Retorna o saldo de crétitos do cliente" + } + }, + "/api/v2/clients/{id}/credits/transfers": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Extrato de créditos", + "tags": [ + "Clientes" + ], + "responses": { + "200": { + "description": "Quando as transferências são listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "from": { + "type": "object", + "properties": { + "account": { + "type": "string" + }, + "amount": { + "type": "number" + } + } + }, + "to": { + "type": "object", + "properties": { + "account": { + "type": "string" + }, + "amount": { + "type": "number" + } + } + } + } + } + } + } + } + }, + "404": { + "description": "Quando o cliente não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-clients-id-credits-transfers", + "description": "Retorna as transfertências de crétidos realizadas" + } + }, + "/api/v2/clients/{id}/bonuses": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true, + "description": "Código do cliente" + } + ], + "get": { + "summary": "Lista os bônus", + "tags": [ + "Clientes" + ], + "responses": { + "200": { + "description": "Quando os bônus são listados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Bonus.v1" + } + } + } + } + } + }, + "operationId": "get-api-v2-clients-id-bonuses", + "description": "Lista os bônus do cliente que ainda não foram utilizados", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "page", + "description": "Número da página" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "per_page", + "description": "Registros por página" + } + ] + } + }, + "/api/v2/clients/{id}/remove_personal_data": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true, + "description": "" + } + ], + "patch": { + "summary": "Solicitação de esquecimento", + "operationId": "patch-api-v2-clients-id-remove-personal-data", + "responses": { + "204": { + "description": "Quando o cliente é marcado para ter seus dados pessoais removidos" + }, + "404": { + "description": "Quando o cliente não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Clientes" + ], + "description": "Solicita a remoção (esquecimento) dos dados pessoais de um cliente, de acordo com a LGPD" + } + }, + "/api/v2/auth/email/{token}": { + "get": { + "summary": "Faz login do cliente por token", + "operationId": "get-api-v2-auth-email-token", + "responses": { + "200": { + "description": "Quanto o login é feito com sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "token": { + "type": "string" + } + }, + "required": [ + "id", + "token" + ] + } + } + } + }, + "401": { + "description": "Quando o token do email é inválido ou expirou" + } + }, + "tags": [ + "Clientes" + ], + "description": "Faz o login do cliente pelo token salvo no campo auth_token", + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "token", + "in": "path", + "required": true + } + ] + } + }, + "/api/v2/auth/client": { + "post": { + "summary": "Faz login do cliente", + "operationId": "post-api-v2-auth-client", + "responses": { + "200": { + "description": "Quanto o login é feito com sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "auth_token": { + "type": "string" + } + }, + "required": [ + "id", + "auth_token" + ] + } + } + } + }, + "400": { + "description": "Quando o email e/ou a senha estão vazios", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "enum": [ + "'email' is mandatory", + "'password' is mandatory" + ] + } + }, + "required": [ + "error" + ] + } + } + } + }, + "422": { + "description": "Quando o cliente não é encontrado", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string", + "enum": [ + "email and/or password invalid" + ] + } + }, + "required": [ + "error" + ] + } + } + } + } + }, + "tags": [ + "Clientes" + ], + "description": "Faz o login do cliente por usuário e senha", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string" + } + }, + "required": [ + "email", + "password" + ] + } + } + } + } + } + }, + "/api/v2/carts/{cart_id}/payment": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "cart_id", + "in": "path", + "required": true + } + ], + "post": { + "summary": "Faz o pagamento do carrinho", + "operationId": "post-api-v2-carts-cart-payment_id-payment", + "responses": { + "301": { + "description": "Quando o pagamento é processado corretamente", + "headers": { + "X-Attempt-Count": { + "schema": { + "type": "number" + }, + "description": "Número de tentativas de pagamento feitas para o carrinho" + }, + "Location": { + "schema": { + "type": "string" + }, + "description": "URL do pedido na API" + } + } + }, + "400": { + "description": "Quando o carrinho não pode ser pago", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "examples": { + "Itens indisponíveis": { + "value": { + "error": "Os itens do carrinho não estão mais disponíveis" + } + }, + "Alteração nos preços dos itens": { + "value": { + "error": "Houve uma alteração nos valores do carrinho" + } + } + } + } + } + }, + "404": { + "description": "Quando o carrinho não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "example-1": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "description": "Faz o pagamento do carrinho usando a forma de pagamento informada", + "tags": [ + "Pagamentos" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "payment_method": { + "type": "string", + "enum": [ + "pix" + ], + "description": "Meio de pagamento" + }, + "channel": { + "type": "string", + "default": "ecommerce", + "enum": [ + "ecommerce", + "direct" + ], + "description": "Canal de venda do carrinho" + } + }, + "required": [ + "payment_method" + ] + } + } + }, + "description": "" + } + } + }, + "/api/v2/menus": { + "get": { + "summary": "Lista os menus", + "operationId": "get-api-v2-menus", + "tags": [ + "Menus" + ], + "responses": { + "200": { + "description": "Quando os menus são listados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Menu.v1" + } + } + } + } + } + }, + "description": "Lista os menus", + "parameters": [ + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "parent_id" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "position" + } + ] + }, + "post": { + "summary": "Cria um menu", + "operationId": "post-api-v2-menus", + "tags": [ + "Menus" + ], + "responses": { + "201": { + "description": "Quando o menu é criado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Menu.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "description": "Cria um menu", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "tooltip": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "page_id": { + "type": "integer" + }, + "parent_id": { + "type": "integer" + }, + "position": { + "type": "string" + }, + "new_position": { + "type": "string" + }, + "external": { + "type": "boolean" + }, + "tag_id": { + "type": "integer" + } + }, + "required": [ + "label", + "position", + "type" + ] + } + } + } + } + } + }, + "/api/v2/menus/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Retorna um menu", + "tags": [ + "Menus" + ], + "responses": { + "200": { + "description": "Quando o menu é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Menu.v1" + } + } + } + }, + "404": { + "description": "Quando o menu não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-menus-id", + "description": "Retorna um menu" + }, + "patch": { + "summary": "Atualiza um menu", + "operationId": "patch-api-v2-menus-id", + "responses": { + "204": { + "description": "Quando o menu é atualizado" + }, + "404": { + "description": "Quando o menu não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Menus" + ], + "description": "Atualiza um menu", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "tooltip": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "page_id": { + "type": "integer" + }, + "parent_id": { + "type": "integer" + }, + "position": { + "type": "string" + }, + "new_position": { + "type": "string" + }, + "external": { + "type": "boolean" + }, + "tag_id": { + "type": "integer" + } + }, + "required": [ + "label", + "position", + "type" + ] + } + } + } + } + }, + "delete": { + "summary": "Remove um menu", + "operationId": "delete-api-v2-menus-id", + "responses": { + "204": { + "description": "Quando o menu é removido" + }, + "404": { + "description": "Quando o menu não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Menus" + ], + "description": "Remove um menu" + } + }, + "/api/v2/menus/positions": { + "get": { + "summary": "Lista as posições dos menus", + "operationId": "get-api-v2-menus-positions", + "tags": [ + "Menus" + ], + "responses": { + "200": { + "description": "Quando as posições são listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "examples": { + "example-1": { + "value": [ + "principal", + "submenu" + ] + } + } + } + } + } + }, + "description": "Lista as posições dos menus" + } + }, + "/api/v2/menus/reorder": { + "post": { + "summary": "Reordena os menus", + "operationId": "post-api-v2-menus-reorder", + "tags": [ + "Menus" + ], + "responses": { + "200": { + "description": "Quando os menus são reordenados" + } + }, + "description": "Reordena os menus na ordem em que seus ids são listados no request", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "description": "A ordem dos elementos será replicada para os menus", + "items": { + "type": "integer" + } + } + }, + "required": [ + "ids" + ] + }, + "examples": { + "example-1": { + "value": { + "ids": [ + 32, + 29, + 28, + 31, + 30, + 27 + ] + } + } + } + } + } + } + } + }, + "/api/v2/menus/tree": { + "get": { + "summary": "Retorna os menus em árvore", + "operationId": "get-api-v2-menus-trees", + "tags": [ + "Menus" + ], + "responses": { + "200": { + "description": "Quando os menus são listados", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "description": "Posição", + "items": { + "$ref": "#/components/schemas/Menu_in_tree.v1" + } + } + } + } + } + } + }, + "description": "Retorna os menus em árvore, organizados pela posição" + } + }, + "/api/v2/site_message": { + "get": { + "summary": "Retorna uma mensagem do site", + "tags": [ + "Mensagens do site" + ], + "responses": { + "200": { + "description": "Quando a mensagem do site existe", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Site_message.v1" + } + } + } + }, + "404": { + "description": "Quando a mensagem do site não existe", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-site-message", + "description": "Retorna uma mensagem do site" + }, + "patch": { + "summary": "Cria ou atualiza uma mensagem do site", + "operationId": "post-api-v2-site-message", + "tags": [ + "Mensagens do site" + ], + "responses": { + "204": { + "description": "Quando a mensagem é criada ou atualizada" + } + }, + "description": "Cria ou atualiza uma mensagem do site", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "call_to_action": { + "type": "string" + } + } + } + } + } + } + }, + "delete": { + "summary": "Remove uma mensagem do site", + "operationId": "delete-api-v2-site-message", + "responses": { + "204": { + "description": "Quando a mensagem é ou não removida" + }, + "404": { + "description": "Quando o menu não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Mensagens do site" + ], + "description": "Remove uma mensagem do site" + } + }, + "/api/v2/shop/images": { + "get": { + "summary": "Lista as images", + "tags": [ + "Loja" + ], + "responses": { + "200": { + "description": "Quando as imagens são listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Shop_asset.v1" + } + } + } + } + } + }, + "operationId": "get-api-v2-shop-images", + "description": "Lista as imagens associadas a loja", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/per_page" + }, + { + "$ref": "#/components/parameters/sort" + } + ] + }, + "post": { + "summary": "Cria uma imagem", + "operationId": "post-api-v2-shop-images", + "responses": { + "201": { + "description": "Quando a imagem é criada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Shop_asset.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviado são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Loja" + ], + "description": "Permite cadastrar uma imagem", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "position": { + "type": "string" + }, + "file_uid": { + "type": "string" + } + } + } + } + } + } + } + }, + "/api/v2/shop/images/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "delete": { + "summary": "Remove uma imagem", + "operationId": "delete-api-v2-shop-images-id", + "responses": { + "204": { + "description": "Quando a imagem é removida" + }, + "404": { + "description": "Quando a imagem não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "description": "Permite remover uma imagem da loja", + "tags": [ + "Loja" + ] + } + }, + "/api/v2/shop/product_attributes": { + "post": { + "summary": "Cria um atributo customizado de produto", + "operationId": "post-api-v2-shop-product-attributes", + "responses": { + "201": { + "description": "Quando o atributo customizado de produto é criado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Products_attributes.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "description": "Permite adicionar um atributo customizado de produto", + "tags": [ + "Loja" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "index": { + "type": "number" + }, + "name": { + "type": "string" + }, + "mandatory": { + "type": "boolean" + } + }, + "required": [ + "index", + "name", + "mandatory" + ] + } + } + }, + "description": "Quando o atributo customizado é criado" + } + }, + "parameters": [] + }, + "/api/v2/customizations": { + "get": { + "summary": "Lista as personalizações", + "responses": { + "200": { + "description": "Quando as personalizações são listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Customization.v1" + } + } + } + } + } + }, + "operationId": "get-api-v2-customizations", + "description": "Permite listar as personalizações", + "tags": [ + "Personalizações" + ], + "parameters": [ + { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "product_id", + "description": "Filtra por produto" + } + ] + }, + "post": { + "summary": "Cria uma personalização", + "operationId": "post-api-v2-customizations", + "responses": { + "201": { + "description": "Quando a personalização é criada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Customization.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "description": "Permite criar uma personalização", + "tags": [ + "Personalizações" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "group_name": { + "type": "string" + }, + "group_type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "label": { + "type": "string" + }, + "image_uid": { + "type": "string" + }, + "image_name": { + "type": "string" + }, + "price": { + "type": "number", + "default": 0 + }, + "quantity": { + "type": "integer", + "default": 0 + }, + "handling_days": { + "type": "integer", + "default": 0 + }, + "tag_id": { + "type": "integer" + }, + "sku": { + "type": "string" + }, + "pattern": { + "type": "string" + } + }, + "required": [ + "group_name", + "group_type", + "name", + "tag_id" + ] + } + } + }, + "description": "" + } + }, + "parameters": [] + }, + "/api/v2/customizations/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "delete": { + "summary": "Remove uma personalização", + "operationId": "delete-api-v2-customizations-id", + "responses": { + "204": { + "description": "Quando a personalização é removida" + }, + "404": { + "description": "Quando a personalização não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Personalizações" + ], + "description": "Permite remover uma personalização" + }, + "patch": { + "summary": "Altera uma personalização", + "operationId": "patch-api-v2-customizations-id", + "responses": { + "204": { + "description": "Quando a personalização é alterada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Customization.v1" + } + } + } + }, + "404": { + "description": "Quando a personalização não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "description": "Permite alterar uma personalização", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "group_name": { + "type": "string" + }, + "group_type": { + "type": "string" + }, + "name": { + "type": "string" + }, + "label": { + "type": "string" + }, + "image_uid": { + "type": "string" + }, + "image_name": { + "type": "string" + }, + "price": { + "type": "string" + }, + "quantity": { + "type": "string" + }, + "handling_days": { + "type": "string" + }, + "tag_id": { + "type": "string" + }, + "sku": { + "type": "string" + }, + "pattern": { + "type": "string" + } + } + } + } + } + }, + "tags": [ + "Personalizações" + ] + }, + "get": { + "summary": "Retorna uma personalização", + "operationId": "get-api-v2-customizations-id", + "responses": { + "200": { + "description": "Quando a personalização é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Customization.v1" + } + } + } + }, + "404": { + "description": "Quando a personalização não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Personalizações" + ], + "description": "Permite retornar uma personalização" + } + }, + "/api/v2/orders/{order_id}/items": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "order_id", + "in": "path", + "required": true + }, + { + "schema": { + "type": "boolean", + "default": false + }, + "in": "query", + "name": "include_customizations_in_total", + "description": "Inclui o preço dos produtos customizados no total do pedido" + } + ], + "get": { + "summary": "Lista os itens do pedido", + "responses": { + "200": { + "description": "Quando os itens do pedido são listados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Order_items.v1" + } + } + } + } + }, + "404": { + "description": "Quando a lista de itens do pedido não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-orders-order_id-items", + "tags": [ + "Pedidos" + ], + "description": "Permite listar os itens do pedido" + } + }, + "/api/v2/orders/{order_id}/items/{item_id}/customizations": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "order_id", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "item_id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Lista as personalizações do item do pedido", + "responses": { + "200": { + "description": "Quando as personalizações são listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Order_item_customization.v1" + } + }, + "examples": { + "Com pesonalizações": { + "value": [ + { + "id": 1, + "number": 1, + "group_name": "Color", + "sku": "A1", + "name": "Red", + "price": 0, + "intl_price": 0, + "handling_days": 0 + } + ] + }, + "Sem personalizações": { + "value": [] + } + } + } + } + }, + "404": { + "description": "Quando o item não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-orders-order_id-items-item_id-customizations", + "tags": [ + "Pedidos" + ], + "description": "Permite listar as personalizações de cada item do pedido" + } + }, + "/api/v2/carts/{cart_id}/items/{item_id}/customizations": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "cart_id", + "in": "path", + "required": true + }, + { + "schema": { + "type": "string" + }, + "name": "item_id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Lista as personalizações do item do carrinho", + "responses": { + "200": { + "$ref": "#/components/responses/CartItemCustomizationList" + }, + "404": { + "description": "Quando o item não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-carts-cart_id-items-item_id-customizations", + "tags": [ + "Carrinhos" + ], + "description": "Permite listar as personalizações de cada item do carrinho" + }, + "delete": { + "summary": "Remove uma personalização do item do carrinho", + "operationId": "delete-api-v2-carts-cart_id-items-item_id-customizations", + "responses": { + "204": { + "description": "Quando a personalização é removida" + }, + "404": { + "description": "Quando a personalização não é encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "description": "Permite remover uma customização do item do carrinho", + "tags": [ + "Carrinhos" + ] + } + }, + "/api/v2/mappings": { + "get": { + "summary": "Lista os mapeamentos", + "tags": [ + "Mapeamentos" + ], + "responses": { + "200": { + "$ref": "#/components/responses/Mappings" + } + }, + "operationId": "get-api-v2-mappings", + "description": "Lista os mapeamentos", + "parameters": [ + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/per_page" + } + ] + }, + "post": { + "summary": "Cria um mapeamento", + "operationId": "post-api-v2-mappings", + "responses": { + "201": { + "$ref": "#/components/responses/MappingCreate" + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "tags": [ + "Mapeamentos" + ], + "description": "Cria um mapeamento", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "from": { + "type": "array", + "items": { + "type": "string" + } + }, + "to": { + "type": "string" + } + }, + "required": [ + "key" + ] + } + } + } + } + } + }, + "/api/v2/mappings/{id}": { + "parameters": [ + { + "schema": { + "type": "integer" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Retorna um mapeamento", + "tags": [ + "Mapeamentos" + ], + "responses": { + "200": { + "$ref": "#/components/responses/Mapping" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "operationId": "get-api-v2-mappings-id", + "description": "Retorna os dados de um mapeamento" + }, + "patch": { + "summary": "Atualiza um mapeamento", + "operationId": "patch-api-v2-mappings-id", + "responses": { + "204": { + "description": "Quando o mapeamento é atualizado" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "tags": [ + "Mapeamentos" + ], + "description": "Atualiza um mapeamento", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "from": { + "type": "array", + "items": { + "type": "string" + } + }, + "to": { + "type": "string" + } + }, + "required": [ + "key" + ] + } + } + } + } + }, + "delete": { + "summary": "Remove um mapeamento", + "operationId": "delete-api-v2-mappings-id", + "responses": { + "204": { + "description": "Quando o mapeamento é removido" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "tags": [ + "Mapeamentos" + ], + "description": "Remove um mapeamento" + } + }, + "/api/v2/banners": { + "get": { + "summary": "Lista os banners", + "responses": { + "200": { + "$ref": "#/components/responses/Banners" + } + }, + "operationId": "get-api-v2-banners", + "description": "Retorna a lista de banners", + "tags": [ + "Mídias" + ], + "parameters": [ + { + "$ref": "#/components/parameters/only_valid" + }, + { + "$ref": "#/components/parameters/only_expired" + }, + { + "$ref": "#/components/parameters/only_scheduled" + }, + { + "$ref": "#/components/parameters/tag" + }, + { + "$ref": "#/components/parameters/title" + }, + { + "$ref": "#/components/parameters/no_paginate" + }, + { + "$ref": "#/components/parameters/page" + }, + { + "$ref": "#/components/parameters/per_page" + } + ] + } + }, + "/api/v2/banners/{id}": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Retorna um banner", + "tags": [ + "Mídias" + ], + "responses": { + "200": { + "$ref": "#/components/responses/Banner" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "operationId": "get-api-v2-banners-id", + "description": "Retorna os dados de um banner" + } + }, + "/api/v2/banners/all": { + "get": { + "summary": "Retorna os banners agrupados por tag", + "tags": [ + "Mídias" + ], + "responses": { + "200": { + "$ref": "#/components/responses/AllBanners" + } + }, + "operationId": "get-api-v2-banners-all", + "parameters": [], + "description": "Retorna todos os banners disponíveis agrupados por tag" + }, + "parameters": [] + }, + "/api/v2/carts/{cart_id}/shipping_methods/intl": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "cart_id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Cálculo de frete internacional", + "tags": [ + "Carrinhos" + ], + "responses": { + "200": { + "description": "Quando as formas de entrega são retornadas", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "{package_label}": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Shipping_methods.v1" + } + } + } + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "operationId": "get-api-v2-carts-cart_id-shipping_methods-intl", + "description": "Permite calcular o frete para pedidos internacionais", + "parameters": [ + { + "schema": { + "type": "string", + "example": "BRA", + "pattern": "^[A-Z]{3}$" + }, + "in": "query", + "name": "country", + "description": "Código do país de destino", + "required": true + } + ] + } + }, + "/api/v2/carts/{cart_id}/samples": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "cart_id", + "in": "path", + "required": true + } + ], + "get": { + "summary": "Lista as amostras", + "tags": [ + "Carrinhos" + ], + "responses": { + "200": { + "description": "Quando as amostras são listadas", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "image_url": { + "type": "string", + "nullable": true + }, + "name": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "url": { + "type": "string" + }, + "variants": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "main": { + "type": "boolean" + }, + "sku": { + "type": "string" + }, + "name": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "image_url": { + "type": "string", + "nullable": true + }, + "product_id": { + "type": "integer" + }, + "norder": { + "type": "integer" + } + }, + "required": [ + "id", + "main", + "sku", + "name", + "updated_at", + "image_url", + "product_id", + "norder" + ] + } + } + }, + "required": [ + "id", + "image_url", + "name", + "reference", + "updated_at", + "url", + "variants" + ] + } + } + } + }, + "404": { + "description": "Quando um carrinho não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "operationId": "get-api-v2-carts-cart-id-samples", + "description": "Lista as amostras disponíveis para determinado carrinho" + } + }, + "/api/v2/carts/{cart_id}/shipping_address": { + "parameters": [ + { + "$ref": "#/components/parameters/cart_id" + } + ], + "get": { + "summary": "Endereço de entrega", + "tags": [ + "Carrinhos" + ], + "responses": { + "200": { + "description": "Quando o endereço é retornado", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "company_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "documents": { + "type": "object", + "description": "Serão retornados apenas os campos preenchidos", + "properties": { + "cpf": { + "type": "string" + }, + "cnpj": { + "type": "string" + }, + "ie": { + "type": "string" + } + } + }, + "street_name": { + "type": "string" + }, + "street_number": { + "type": "string", + "example": "188A" + }, + "complement": { + "type": "string" + }, + "neighborhood": { + "type": "string" + }, + "first_phone_area": { + "type": "string", + "description": "Somente números", + "example": "11" + }, + "first_phone": { + "type": "string", + "description": "Somente números", + "example": "984453322" + }, + "second_phone_area": { + "type": "string", + "description": "Somente números" + }, + "second_phone": { + "type": "string", + "description": "Somente números" + }, + "reference": { + "type": "string" + }, + "zip": { + "type": "string", + "description": "Somente números", + "example": "90050000" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string", + "example": "RS", + "minLength": 2, + "maxLength": 2 + }, + "recipient_name": { + "type": "string" + } + }, + "required": [ + "first_name", + "last_name", + "email", + "street_name", + "street_number", + "neighborhood", + "first_phone_area", + "first_phone", + "zip", + "city", + "state" + ], + "$ref": "#/components/schemas/Shipping_address" + } + } + } + }, + "404": { + "description": "Quando o carrinho não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "operationId": "get-api-v2-carts-cart_id-shipping_address", + "description": "Retorna o endereço de entrega" + }, + "post": { + "summary": "Adiciona um endereço de entrega", + "description": "Adiciona um endereço de entrega no carrinho", + "tags": [ + "Envio do carrinho" + ], + "operationId": "post-api-v2-carts-cart_id-shipping_address", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Shipping_address" + } + } + } + }, + "responses": { + "201": { + "description": "Endereço adicionado com sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart_item.v1" + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + } + } + }, + "/api/v2/carts/{cart_id}/coupon_code": { + "parameters": [ + { + "$ref": "#/components/parameters/cart_id" + } + ], + "post": { + "summary": "Associa código de cupom ao carrinho", + "tags": [ + "Carrinhos" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Código do cupom" + } + }, + "required": [ + "code" + ] + } + } + } + }, + "responses": { + "200": { + "description": "Quando o cupom é associado", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Código do cupom" + }, + "discount": { + "type": "number" + }, + "rebate_token": { + "type": "string" + }, + "rebate_discount": { + "type": "number" + } + }, + "required": [ + "code", + "discount", + "rebate_token", + "rebate_discount" + ] + } + } + } + }, + "404": { + "description": "Quando o carrinho ou o desconto não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "operationId": "post-api-v2-carts-cart_id-coupon_code", + "description": "Associa um código de cupom ao carrinho" + } + }, + "/api/v2/orders/channels": { + "get": { + "summary": "Lista os canais dos pedidos", + "tags": [ + "Pedidos" + ], + "responses": { + "200": { + "$ref": "#/components/responses/Channels" + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "operationId": "get-api-v2-orders-channels", + "description": "Lista todos os channels usados nos pedidos criados" + } + }, + "/api/v2/orders/states": { + "get": { + "summary": "Lista os estados dos pedidos", + "tags": [ + "Pedidos" + ], + "responses": { + "200": { + "$ref": "#/components/responses/States" + }, + "404": { + "description": "Domínio de loja não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "get-api-v2-orders-states", + "description": "Lista todos os estados usados nos pedidos criados", + "security": [ + { + "Token": [] + } + ] + } + }, + "/api/v2/products/{product_id}/price": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "product_id", + "in": "path", + "required": true, + "description": "ID do produto" + } + ], + "get": { + "summary": "Lista os preços do produto", + "tags": [ + "Produtos" + ], + "responses": { + "200": { + "$ref": "#/components/responses/ProductPrice" + } + }, + "operationId": "get-api-v2-products-product_id-price", + "description": "Retorna o preço do produto e das variantes", + "security": [ + { + "Token": [] + } + ], + "parameters": [ + { + "$ref": "#/components/parameters/coupon_codes" + } + ] + } + }, + "/api/v2/products/{product_id}/images": { + "parameters": [ + { + "$ref": "#/components/parameters/product_id" + } + ], + "get": { + "summary": "Lista as imagens do produto", + "tags": [ + "Produtos" + ], + "responses": { + "200": { + "$ref": "#/components/responses/ProductImages" + } + }, + "operationId": "get-api-v2-products-product_id-images", + "description": "Lista as imagens do produto" + }, + "post": { + "summary": "Cria uma imagem do produto", + "tags": [ + "Produtos" + ], + "responses": { + "201": { + "$ref": "#/components/responses/ProductImage" + }, + "404": { + "description": "Quando o produto não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "file_url": { + "type": "string", + "format": "uri" + }, + "variant_ids": { + "type": "array", + "description": "IDs da variantes associadas a imagem", + "items": { + "type": "integer" + } + } + }, + "required": [ + "file_url" + ] + } + } + } + }, + "operationId": "post-api-v2-products-product_id-images", + "description": "Cria uma imagem do produto" + } + }, + "/api/v2/products/{product_id}/images/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/product_id" + }, + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "delete": { + "summary": "Deleta uma imagem do produto", + "tags": [ + "Produtos" + ], + "responses": { + "204": { + "description": "Quando a imagem é deletada" + }, + "404": { + "description": "Quando o produto ou a imagem não são encontrados", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "delete-api-v2-products-product_id-images-id", + "description": "Deleta uma imagem do produto" + } + }, + "/api/v2/products/{product_id}/images/reorder": { + "parameters": [ + { + "$ref": "#/components/parameters/product_id" + } + ], + "post": { + "summary": "Reordena imagens do produto", + "tags": [ + "Produtos" + ], + "responses": { + "200": { + "description": "Quando as imagens são reordenadas" + }, + "404": { + "description": "Quando o produto não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + } + } + }, + "required": [ + "ids" + ] + } + } + } + }, + "operationId": "post-api-v2-products-product_id-images-reorder", + "description": "Reordena as imagens do produto" + } + }, + "/api/v2/products/{product_id}/images/{id}/add_variant": { + "parameters": [ + { + "$ref": "#/components/parameters/product_id" + }, + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "post": { + "summary": "Associa imagem com variante", + "tags": [ + "Produtos" + ], + "responses": { + "200": { + "description": "Quando a imagem é associada com a variante" + }, + "404": { + "description": "Quando o produto ou a imagem não são encontrados", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "variant_id": { + "type": "integer" + } + }, + "required": [ + "variant_id" + ] + } + } + } + }, + "operationId": "post-api-v2-products-product_id-images-id-add_variant", + "description": "Associa a imagem com uma variante" + } + }, + "/api/v2/products/{product_id}/images/{id}/remove_variant": { + "parameters": [ + { + "$ref": "#/components/parameters/product_id" + }, + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true + } + ], + "post": { + "summary": "Desassocia imagem da variante", + "tags": [ + "Produtos" + ], + "responses": { + "200": { + "description": "Quando a imagem é desassociada da variante" + }, + "404": { + "description": "Quando o produto ou a imagem não são encontrados", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "variant_id": { + "type": "integer" + } + }, + "required": [ + "variant_id" + ] + } + } + } + }, + "operationId": "post-api-v2-products-product_id-images-id-remove_variant", + "description": "Remove a associação da imagem com uma variante" + } + }, + "/api/v2/orders/{order_code}/packages": { + "parameters": [ + { + "$ref": "#/components/parameters/order_code" + } + ], + "get": { + "summary": "Lista os pacotes de um pedido", + "tags": [ + "Pacotes" + ], + "responses": { + "200": { + "$ref": "#/components/responses/Packages" + }, + "404": { + "description": "Pedido ou pacote não encontrados", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + }, + "operationId": "get-api-v2-orders-order_code-packages", + "description": "Retorna uma lista de pacotes de um pedido", + "security": [ + { + "Token": [] + } + ] + } + }, + "/api/v2/events": { + "post": { + "summary": "Dispara eventos", + "operationId": "post-api-v2-events", + "responses": { + "204": { + "description": "Quando o evento é recebido" + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Eventos" + ], + "description": "Indica para a API que dererminado evento aconteceu e que ela deve disparar as ações relacionadas", + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "event_type", + "required": true, + "description": "Evento que ocorreu" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "id", + "description": "ID do recurso selacionado ao evento", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "browser_ip", + "description": "IP do usuário" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "user_agent", + "description": "User agent do usuário" + } + ] + } + }, + "/api/v2/users/{id}/payables": { + "get": { + "tags": [ + "Recebíveis de usuários" + ], + "summary": "Lista os recebíveis de um usuário pelo ID", + "responses": { + "200": { + "description": "Lista de recebíveis do usuário", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Payables.v1" + } + } + } + } + }, + "404": { + "description": "Quando o usuário não está cadastrado como recebedor", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "description": "Permite a listagem de recebíveis (comissão) de um usuário vendedor da loja, quando ocorre split de pagamentos via Pagarme ", + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id", + "in": "path", + "required": true, + "description": "Código idenficador de usuário" + } + ], + "operationId": "get-api-v2-users-id-payables" + } + }, + "/api/v2/products/{product_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/product_id" + } + ], + "get": { + "summary": "Retorna um produto", + "tags": [ + "Produto" + ], + "responses": { + "200": { + "description": "Produto encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product.v1" + }, + "examples": { + "example-1": { + "value": { + "id": 0, + "active": true, + "available": true, + "category_tags": [ + { + "tag_type": "string", + "name": "string", + "title": "string" + } + ], + "description": "string", + "discount_id": 0, + "html_description": "string", + "image_url": "string", + "installments": [ + 0 + ], + "min_quantity": "string", + "name": "string", + "on_sale": true, + "plain_description": "string", + "price": 0, + "rating": { + "rating": 0, + "votes": 0 + }, + "reference": "string", + "sale_price": 0, + "slug": "string", + "tag_names": [ + "string" + ], + "updated_at": "string", + "url": "string", + "variants": [ + { + "{id}": { + "available": true, + "available_quantity": 0, + "custom_attributes": {}, + "handling_days": 0, + "height": 0, + "id": 1, + "image_url": "string", + "installments": [ + 0 + ], + "inventories": [ + { + "created_at": "2019-08-24T14:15:22Z", + "id": 0, + "name": null, + "place_id": 0, + "price": 0, + "quantity": 0, + "quantity_sold": 0, + "sale_price": 0, + "slug": "string", + "updated_at": "2019-08-24T14:15:22Z", + "variant_id": 0, + "place_name": "string" + } + ], + "length": 0, + "main": true, + "min_quantity": 0, + "name": "string", + "norder": 0, + "price": 0, + "product_id": 0, + "properties": { + "property1": { + "defining": true, + "name": "string", + "value": "string" + }, + "property2": { + "defining": true, + "name": "string", + "value": "string" + }, + "property3": { + "defining": true, + "name": "string", + "value": "string" + } + }, + "quantity": 0, + "quantity_sold": 0, + "sale_price": 0, + "sku": "string", + "slug": "string", + "stock": 0, + "updated_at": "2019-08-24T14:15:22Z", + "weight": 0, + "width": 0 + } + } + ], + "discount_rule": null, + "images": [ + { + "id": 0, + "url": "string", + "updated_at": "2019-08-24T14:15:22Z", + "variant_ids": [ + 0 + ] + } + ] + } + } + } + } + } + }, + "404": { + "description": "Produto não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product.v1" + } + } + } + } + }, + "operationId": "get-api-v2-products-id", + "description": "Retorna um produto pelo código identificador (`product_id`)", + "parameters": [ + { + "$ref": "#/components/parameters/coupon_codes", + "description": "Cupons para calcular o desconto no produto consultado" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include_inventory_place", + "description": "Selecione `true` para incluir o nome do local de armazenamento no retorno da requisição" + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "include_images", + "description": "Selecione `true` para incluir todas as imagens do produto" + } + ] + }, + "patch": { + "summary": "Atualiza um produto", + "operationId": "patch-api-v2-products-id", + "responses": { + "204": { + "description": "Produto atualizado" + }, + "404": { + "description": "Produto não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Parâmetros enviados inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Produto" + ], + "description": "Atualiza informações de um produto no catálogo pelo código identificador (`product_id`)", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SimpleProduct" + } + } + } + } + }, + "delete": { + "summary": "Remove um produto", + "operationId": "delete-api-v2-products-id", + "responses": { + "204": { + "description": "Produto removido" + }, + "404": { + "description": "Produto não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Produto" + ], + "description": "Remove um produto do catálogo pelo código indentificador (`product_id`)" + } + }, + "/api/v2/products/{product_id}/rate": { + "post": { + "summary": "Avalia um produto", + "tags": [ + "Produto" + ], + "responses": { + "200": { + "description": "Avaliação enviada", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "rating": { + "type": "string", + "description": "Média das avaliações" + }, + "votes": { + "type": "string", + "description": "Número de avaliações recebidas" + } + } + }, + "examples": { + "example-1": { + "value": { + "rating": "0.9", + "votes": "2" + } + } + } + } + } + }, + "400": { + "description": "Parâmetros enviados inválidos", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "examples": { + "example-1": { + "value": { + "error": "invalid rate value" + } + } + } + } + } + }, + "404": { + "description": "Produto não possui variantes", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "examples": { + "example-1": { + "value": { + "error": "product without variants" + } + } + } + } + } + } + }, + "operationId": "get-api-v2-products-id-rate", + "description": "Recebe uma avaliação e recalcula a pontuação atual", + "parameters": [ + { + "schema": { + "type": "integer", + "minimum": 0, + "maximum": 5 + }, + "in": "query", + "name": "rate", + "description": "Avaliação" + } + ] + }, + "parameters": [ + { + "$ref": "#/components/parameters/product_id" + } + ] + }, + "/api/v2/products/{product_id}/variants/{variant_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/product_id" + }, + { + "schema": { + "type": "string" + }, + "name": "variant_id", + "in": "path", + "required": true, + "description": "Código identificador da variante" + } + ], + "patch": { + "summary": "Atualiza uma variante", + "operationId": "patch-api-v2-products-product_id-variants-id", + "responses": { + "204": { + "description": "Variante atualizada" + }, + "404": { + "description": "Variante não existente", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Parâmetros enviados inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Variante de produto" + ], + "description": "Atualiza as informações de um variante", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "name": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "main": { + "type": "boolean" + }, + "weight": { + "type": "number", + "description": "Massa do produto, em gramas" + }, + "width": { + "type": "number", + "description": "Largura do produto, em centímetros" + }, + "height": { + "type": "number", + "description": "Altura do produto, em centímetros" + }, + "length": { + "type": "number", + "description": "Comprimento do produito, em centímetros" + }, + "handling_days": { + "type": "integer", + "description": "Dias de manuseio da variante" + }, + "price": { + "type": "number" + }, + "custom_attributes": { + "type": "object", + "description": "Customização da variante" + }, + "min_quantity": { + "type": "integer" + }, + "norder": { + "type": "integer" + }, + "property1": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "defining": { + "type": "boolean" + } + } + }, + "property2": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "defining": { + "type": "boolean" + } + } + }, + "property3": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "defining": { + "type": "boolean" + } + } + }, + "barcode": { + "type": "string" + }, + "quantity_sold": { + "type": "integer", + "description": "Quantidade de itens vendidos" + } + }, + "required": [ + "sku", + "quantity", + "price" + ] + } + } + } + }, + "deprecated": true + }, + "delete": { + "summary": "Remove uma variante", + "operationId": "delete-api-v2-products-product_id-variants-id", + "responses": { + "204": { + "description": "Quando a variante é removida" + }, + "404": { + "description": "Quando a variante não existe", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Variante de produto" + ], + "description": "Permite remover uma variante" + } + }, + "/api/v2/products/{product_id}/images/{image_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/product_id" + }, + { + "$ref": "#/components/parameters/image_id" + } + ], + "delete": { + "summary": "Deleta uma imagem do produto", + "tags": [ + "Imagens de produtos e variantes" + ], + "responses": { + "204": { + "description": "Quando a imagem é deletada" + }, + "404": { + "description": "Quando o produto ou a imagem não são encontrados", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "operationId": "delete-api-v2-products-product_id-images-id", + "description": "Deleta uma imagem do produto" + } + }, + "/api/v2/products/{product_id}/images/{image_id}/add_variant": { + "parameters": [ + { + "$ref": "#/components/parameters/product_id" + }, + { + "$ref": "#/components/parameters/image_id" + } + ], + "post": { + "summary": "Associa imagem com variante", + "tags": [ + "Imagens de produtos e variantes" + ], + "responses": { + "200": { + "description": "Quando a imagem é associada com a variante" + }, + "404": { + "description": "Quando o produto ou a imagem não são encontrados", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "variant_id": { + "type": "integer" + } + }, + "required": [ + "variant_id" + ] + } + } + } + }, + "operationId": "post-api-v2-products-product_id-images-id-add_variant", + "description": "Associa a imagem com uma variante" + } + }, + "/api/v2/products/{product_id}/images/{image_id}/remove_variant": { + "parameters": [ + { + "$ref": "#/components/parameters/product_id" + }, + { + "$ref": "#/components/parameters/image_id" + } + ], + "post": { + "summary": "Desassocia imagem da variante", + "tags": [ + "Imagens de produtos e variantes" + ], + "responses": { + "200": { + "description": "Quando a imagem é desassociada da variante" + }, + "404": { + "description": "Quando o produto ou a imagem não são encontrados", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "variant_id": { + "type": "integer" + } + }, + "required": [ + "variant_id" + ] + } + } + } + }, + "operationId": "post-api-v2-products-product_id-images-id-remove_variant", + "description": "Remove a associação da imagem com uma variante" + } + }, + "/api/v2/carts/{cart_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/Cart.id" + } + ], + "get": { + "summary": "Retorna um carrinho", + "operationId": "get-api-v2-carts-id", + "tags": [ + "Carrinhos da loja" + ], + "description": "Retorna as informações de um carrinho pelo seu `id` ou `token`", + "responses": { + "200": { + "description": "Carrinho encontrado", + "headers": { + "X-Attempt-Count": { + "schema": { + "type": "integer" + }, + "description": "O número de tentativas de pagamento" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart.v1" + } + } + } + }, + "404": { + "description": "Carrinho não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + } + }, + "patch": { + "summary": "Atualiza um carrinho", + "operationId": "patch-api-v2-carts-id", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart.simple" + } + } + } + }, + "tags": [ + "Carrinhos da loja" + ], + "description": "Permite atualizar os atributos de um carrinho", + "responses": { + "204": { + "description": "Carrinho atualizado com sucesso", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart.v1" + } + } + } + }, + "404": { + "description": "Carrinho não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + } + }, + "delete": { + "summary": "Exclui um carrinho", + "operationId": "delete-api-v2-carts-id", + "tags": [ + "Carrinhos da loja" + ], + "description": "Permite excluir um carrinho", + "responses": { + "204": { + "description": "Carrinho excluído com sucesso" + }, + "404": { + "description": "Carrinho não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "404": { + "value": { + "error": "not found" + } + } + } + } + } + } + } + } + }, + "/api/v2/carts/{cart_id}/items/{item_id}": { + "parameters": [ + { + "$ref": "#/components/parameters/Cart.id" + }, + { + "schema": { + "type": "string" + }, + "name": "item_id", + "in": "path", + "required": true + } + ], + "patch": { + "summary": "Atualiza um item do carrinho", + "operationId": "patch-api-v2-carts-cart_id-items-id", + "responses": { + "204": { + "description": "Item do carrinho alterado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Cart.v1" + } + } + } + }, + "404": { + "description": "Item ou o carrinho não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "description": "Atualiza um item do carrinho", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product.v0" + } + } + } + }, + "tags": [ + "Itens do carrinho" + ] + }, + "delete": { + "summary": "Remove um item do carrinho", + "operationId": "delete-api-v2-carts-cart_id-items-id", + "responses": { + "204": { + "description": "Item do carrinho removido" + }, + "404": { + "description": "Item ou o carrinho não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Itens do carrinho" + ], + "description": "Remove um item do carrinho" + } + }, + "/api/v2/carts/{cart_id}/shipping_methods/{value_method}": { + "parameters": [ + { + "$ref": "#/components/parameters/Cart.id" + }, + { + "schema": { + "type": "string" + }, + "name": "value_method", + "in": "path", + "required": true, + "description": "Tipo de envio (`value`)" + } + ], + "patch": { + "summary": "Atualiza o método de envio de um carrinho", + "operationId": "patch-api-v2-carts-cart_id-shipping_methods-value_method", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Shipping_methods.v1" + } + } + } + }, + "tags": [ + "Envio do carrinho" + ], + "description": "Atualiza o método para o envio dos itens do carrinho", + "responses": { + "204": { + "description": "Carrinho atualizado com sucesso" + }, + "404": { + "description": "Carrinho não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + } + } + }, + "/api/v2/carts/{cart_id}/shipping_methods": { + "parameters": [ + { + "$ref": "#/components/parameters/Cart.id" + } + ], + "get": { + "summary": "Cálculo de frete", + "tags": [ + "Envio do carrinho" + ], + "operationId": "get-api-v2-carts-cart_id-shipping_methods", + "description": "Calculo os método de envio disponíveis para o carrinho", + "parameters": [ + { + "$ref": "#/components/parameters/Cart.id" + } + ], + "responses": { + "200": { + "description": "Formas de envio disponíveis retornadas", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "{package_label}": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Shipping_methods.v1" + } + } + } + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + } + } + }, + "/api/v2/carts/{cart_id}/installments": { + "parameters": [ + { + "$ref": "#/components/parameters/Cart.id" + } + ], + "get": { + "summary": "Calcula as parcelas de pagamento", + "tags": [ + "Pagamento" + ], + "operationId": "get-api-v2-carts-cart_id-installments", + "description": "Calcula as parcelas de pagamento para valor total do carrinho", + "responses": { + "200": { + "description": "Carrinho encontrado", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Cart_installment.v1" + } + } + } + } + }, + "404": { + "description": "Carrinho não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + }, + "examples": { + "Não encontrado": { + "value": { + "error": "not found" + } + } + } + } + } + } + } + } + }, + "/api/v2/orders/{order_code}": { + "parameters": [ + { + "$ref": "#/components/parameters/Order.code" + }, + { + "$ref": "#/components/parameters/include_customizations_in_total" + } + ], + "get": { + "summary": "Retorna um pedido", + "tags": [ + "Pedidos" + ], + "operationId": "get-api-v2-orders-order-code", + "description": "Retorna os dados de um pedido pelo `code` ou `token` do pedido", + "parameters": [ + { + "schema": { + "type": "boolean", + "default": false + }, + "in": "query", + "name": "include_shipping_address", + "description": "Inclui as formas de entrega do pedido" + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order.v1" + } + } + } + }, + "404": { + "description": "Pedido não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + } + }, + "patch": { + "summary": "Atualiza dados extras de um pedido", + "operationId": "patch-api-v2-orders-order_code", + "description": "Atualiza o campo de dados extras de um pedido pelo `code` do pedido", + "tags": [ + "Pedidos" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "extra": { + "type": "object", + "description": "Campo para registro de observações, chave ou valores necessários" + } + } + } + } + } + }, + "responses": { + "204": { + "description": "Dado extra alterado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Shipping_address" + } + } + } + }, + "404": { + "description": "Pedido não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + } + } + }, + "/api/v2/orders/{order_code}/events": { + "parameters": [ + { + "$ref": "#/components/parameters/Order.code" + } + ], + "get": { + "summary": "Retorna os eventos ocorridos em um pedido", + "tags": [ + "Pedidos" + ], + "operationId": "get-api-v2-orders-order-code-events", + "description": "Retorna a *timeline* de eventos ocorridos em um pedido", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + }, + "examples": { + "events": { + "value": [ + { + "occurred_at": "2022-12-26T11:53:12.401-03:00", + "name": "Pedido enviado", + "user": "Jessica", + "ip": "172.29.33.150", + "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "created_at": "2022-12-26T11:53:12.402-03:00", + "updated_at": "2022-12-26T11:53:12.402-03:00" + }, + { + "occurred_at": "2022-12-26T11:53:28.136-03:00", + "name": "Pedido entregue", + "user": "Jessica", + "ip": "178.29.79.40", + "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "created_at": "2022-12-26T11:53:28.136-03:00", + "updated_at": "2022-12-26T11:53:28.136-03:00" + } + ] + } + } + } + } + }, + "404": { + "description": "Pedido não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + } + } + }, + "/api/v2/orders/{order_code}/reviews": { + "parameters": [ + { + "$ref": "#/components/parameters/Order.code" + } + ], + "get": { + "summary": "Retorna a avaliação de um pedido", + "tags": [ + "Pedidos" + ], + "operationId": "get-api-v2-orders-order-code-reviews", + "description": "Retorna a avaliação que o cliente fez em um pedido", + "responses": { + "200": { + "description": "Resenhas retornadas" + }, + "404": { + "description": "Pedido não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + } + } + }, + "/api/v2/orders/{order_code}/discounts": { + "parameters": [ + { + "$ref": "#/components/parameters/Order.code" + } + ], + "get": { + "summary": "Retorna os descontos de um pedido", + "tags": [ + "Pedidos" + ], + "operationId": "get-api-v2-orders-order-code-discounts", + "description": "Retorna os descontos de um pedido pelo `code` ou `token` do pedido", + "responses": { + "200": { + "description": "Descontos retornados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": {} + } + }, + "example": { + "Desconto": { + "value": [ + { + "name": "Pagamento via slip", + "valid_to": "payment", + "apply_to": "cart", + "type": "%", + "value": "10,", + "package": null, + "sku": null, + "created_at": "2022-12-02T12:00:03.651-03:00", + "updated_at": "2022-12-02T12:00:03.651-03:00" + } + ] + } + } + } + } + }, + "404": { + "description": "Pedido não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + } + } + }, + "/api/v2/orders/{order_code}/shipping_address": { + "parameters": [ + { + "$ref": "#/components/parameters/Order.code" + } + ], + "get": { + "summary": "Retorna o endereço de envio", + "description": "Retorna o endereço de envio pelo `code` do pedido", + "tags": [ + "Envio de pedido" + ], + "operationId": "get-api-v2-orders-order-code-shipping_address", + "responses": { + "200": { + "description": "Endereço do pedido retornado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Shipping_address" + } + } + } + }, + "404": { + "description": "Pedido não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + } + }, + "patch": { + "summary": "Atualiza endereço do pedido", + "operationId": "patch-api-v2-orders-order-code-shipping-address", + "description": "Atualiza dados de endereço do pedido", + "tags": [ + "Envio de pedido" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Shipping_address" + } + } + } + }, + "responses": { + "204": { + "description": "Endereço do pedido alterado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Shipping_address" + } + } + } + }, + "404": { + "description": "Pedido não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + } + } + }, + "/api/v2/orders/{order_code}/capture": { + "parameters": [ + { + "$ref": "#/components/parameters/Order.code" + } + ], + "post": { + "summary": "Captura um pedido", + "operationId": "post-api-v2-orders-capture", + "description": "Captura o pagamento no adquirente para pedidos com pagamento por cartão de crédito.", + "tags": [ + "Fluxo e andamento" + ], + "responses": { + "200": { + "description": "Captura realizada com sucesso", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + }, + "examples": { + "Pagar.me": { + "value": { + "object": "transaction", + "status": "paid", + "refuse_reason": null, + "status_reason": "acquirer", + "acquirer_response_code": "0000", + "acquirer_name": "pagarme", + "acquirer_id": "5eab10915eab10915eab1091", + "authorization_code": "123456", + "soft_descriptor": "", + "tid": 1234567, + "nsu": 1234567, + "date_created": "2020-05-14T19:14:50.322Z", + "date_updated": "2020-05-15T14:19:34.699Z", + "amount": 1400, + "authorized_amount": 1400, + "paid_amount": 1400, + "refunded_amount": 0, + "installments": 1, + "id": 1234567, + "cost": 120, + "card_holder_name": "John Doe", + "card_last_digits": "6565", + "card_first_digits": "470373", + "card_brand": "visa", + "card_pin_mode": null, + "card_magstripe_fallback": false, + "cvm_pin": false, + "postback_url": "https://demo.vnda.com.br/v2/payments/pagarme/notifications", + "payment_method": "credit_card", + "capture_method": "ecommerce", + "antifraud_score": null, + "boleto_url": null, + "boleto_barcode": null, + "boleto_expiration_date": null, + "referer": "api_key", + "ip": "127.0.0.1", + "subscription_id": null, + "phone": null, + "address": null, + "customer": { + "object": "customer", + "id": 2954669, + "external_id": "example@vnda.com.br", + "type": "individual", + "country": "br", + "document_number": null, + "document_type": "cpf", + "name": "John Doe", + "email": "example@vnda.com.br", + "phone_numbers": [ + "+5511111111111" + ], + "born_at": null, + "birthday": null, + "gender": null, + "date_created": "2020-05-14T19:14:50.248Z", + "documents": [ + { + "object": "document", + "id": "doc_cka75cka75cka75cka75cka75", + "type": "cpf", + "number": 191 + } + ] + }, + "billing": { + "object": "billing", + "id": 1255695, + "name": "John Doe", + "address": { + "object": "address", + "street": "Rua João Neves da Fontoura", + "complementary": null, + "street_number": "1", + "neighborhood": "Azenha", + "city": "Porto Alegre", + "state": "RS", + "zipcode": "90050030", + "country": "br", + "id": 2808888 + } + }, + "shipping": null, + "items": [ + { + "object": "item", + "id": "05.01.4.1.006", + "title": "Aceto Balsâmico Di Modena IGP 500ml Aceto Balsamico Di Modena IGP 500ml", + "unit_price": 1400, + "quantity": 1, + "category": null, + "tangible": true, + "venue": null, + "date": null + } + ], + "card": { + "object": "card", + "id": "card_cka75cka75cka75cka75cka75", + "date_created": "2020-05-14T19:14:50.307Z", + "date_updated": "2020-05-14T19:14:50.717Z", + "brand": "visa", + "holder_name": "f dc", + "first_digits": "470373", + "last_digits": "6565", + "country": "RUSSIA", + "fingerprint": "cka75cka75cka75cka75cka75", + "valid": true, + "expiration_date": "0423" + }, + "split_rules": null, + "metadata": { + "order": "7A4F490570", + "seller-1": { + "name": "default", + "package": "7A4F490570-01" + } + }, + "antifraud_metadata": {}, + "reference_key": null, + "device": null, + "local_transaction_id": null, + "local_time": null, + "fraud_covered": false, + "fraud_reimbursed": null, + "order_id": null, + "risk_level": "very_low", + "receipt_url": null, + "payment": null, + "addition": null, + "discount": null, + "private_label": null + } + } + } + } + } + }, + "404": { + "description": "Pedido não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Captura não efetuada junto ao adquirente", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "examples": { + "failure": { + "value": { + "error": "Capture was unsuccessful" + } + } + } + } + } + } + } + } + }, + "/api/v2/orders/{order_code}/confirm": { + "parameters": [ + { + "$ref": "#/components/parameters/Order.code" + } + ], + "post": { + "summary": "Confirma um pedido", + "operationId": "post-api-v2-orders-order-code-confirm", + "description": "Confirma um pedido", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "confirmation_data": { + "type": "string", + "description": "Parâmetro para incluir o retorno [da requisição de captura do pagamento](https://developers.vnda.com.br/reference/post-api-v2-orders-capture).\nEsse parâmetro é **obrigatório** para pedidos com pagamento por cartão de crédito. " + } + } + }, + "examples": { + "Depósito": { + "value": { + "banco": "Banco do Brasil", + "data_credito": "2020-03-26", + "conferido_por": "Nome do usuário do financeiro" + } + }, + "Cartão de crédito via Pagar.me": { + "value": { + "object": "transaction", + "status": "paid", + "refuse_reason": null, + "status_reason": "acquirer", + "acquirer_response_code": "0000", + "acquirer_name": "pagarme", + "acquirer_id": "5eab10915eab10915eab1091", + "authorization_code": "123456", + "soft_descriptor": "", + "tid": 1234567, + "nsu": 1234567, + "date_created": "2020-05-14T19:14:50.322Z", + "date_updated": "2020-05-15T14:19:34.699Z", + "amount": 1400, + "authorized_amount": 1400, + "paid_amount": 1400, + "refunded_amount": 0, + "installments": 1, + "id": 1234567, + "cost": 120, + "card_holder_name": "John Doe", + "card_last_digits": "6565", + "card_first_digits": "470373", + "card_brand": "visa", + "card_pin_mode": null, + "card_magstripe_fallback": false, + "cvm_pin": false, + "postback_url": "https://demo.vnda.com.br/v2/payments/pagarme/notifications", + "payment_method": "credit_card", + "capture_method": "ecommerce", + "antifraud_score": null, + "boleto_url": null, + "boleto_barcode": null, + "boleto_expiration_date": null, + "referer": "api_key", + "ip": "127.0.0.1", + "subscription_id": null, + "phone": null, + "address": null, + "customer": { + "object": "customer", + "id": 2954669, + "external_id": "example@vnda.com.br", + "type": "individual", + "country": "br", + "document_number": null, + "document_type": "cpf", + "name": "John Doe", + "email": "example@vnda.com.br", + "phone_numbers": [ + "+5511111111111" + ], + "born_at": null, + "birthday": null, + "gender": null, + "date_created": "2020-05-14T19:14:50.248Z", + "documents": [ + { + "object": "document", + "id": "doc_cka75cka75cka75cka75cka75", + "type": "cpf", + "number": 191 + } + ] + }, + "billing": { + "object": "billing", + "id": 1255695, + "name": "John Doe", + "address": { + "object": "address", + "street": "Rua João Neves da Fontoura", + "complementary": null, + "street_number": "1", + "neighborhood": "Azenha", + "city": "Porto Alegre", + "state": "RS", + "zipcode": "90050030", + "country": "br", + "id": 2808888 + } + }, + "shipping": null, + "items": [ + { + "object": "item", + "id": "05.01.4.1.006", + "title": "Aceto Balsâmico Di Modena IGP 500ml Aceto Balsamico Di Modena IGP 500ml", + "unit_price": 1400, + "quantity": 1, + "category": null, + "tangible": true, + "venue": null, + "date": null + } + ], + "card": { + "object": "card", + "id": "card_cka75cka75cka75cka75cka75", + "date_created": "2020-05-14T19:14:50.307Z", + "date_updated": "2020-05-14T19:14:50.717Z", + "brand": "visa", + "holder_name": "f dc", + "first_digits": "470373", + "last_digits": "6565", + "country": "RUSSIA", + "fingerprint": "cka75cka75cka75cka75cka75", + "valid": true, + "expiration_date": "0423" + }, + "split_rules": null, + "metadata": { + "order": "7A4F490570", + "seller-1": { + "name": "default", + "package": "7A4F490570-01" + } + }, + "antifraud_metadata": {}, + "reference_key": null, + "device": null, + "local_transaction_id": null, + "local_time": null, + "fraud_covered": false, + "fraud_reimbursed": null, + "order_id": null, + "risk_level": "very_low", + "receipt_url": null, + "payment": null, + "addition": null, + "discount": null, + "private_label": null + } + } + } + } + }, + "description": "Confirma um pedido" + }, + "tags": [ + "Fluxo e andamento" + ], + "responses": { + "200": { + "description": "Pedido confirmado" + }, + "404": { + "description": "Pedido não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + } + } + }, + "/api/v2/orders/{order_code}/chargeback": { + "parameters": [ + { + "$ref": "#/components/parameters/Order.code" + } + ], + "post": { + "summary": "Estorna pagamento por cartão de crédito", + "operationId": "post-api-v2-orders-order-code-chargeback", + "description": "Faz o estorno do pagamento no adquirente do cartão de crédito\nOperação válida para pedidos pagos com cartão de crédito", + "tags": [ + "Fluxo e andamento" + ], + "responses": { + "200": { + "description": "OK" + }, + "422": { + "$ref": "#/components/responses/422" + } + } + } + }, + "/api/v2/orders/{order_code}/cancel": { + "parameters": [ + { + "$ref": "#/components/parameters/Order.code" + } + ], + "post": { + "summary": "Cancela um pedido", + "operationId": "post-api-v2-orders-order-code-cancel", + "description": "Altera o status do pedido para `cancelado`", + "tags": [ + "Fluxo e andamento" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cancelation_data": { + "type": "string", + "description": "Parâmetro para incluir uma confirmação de estorno de pagamento para o cliente.\nPara pedidos com pagamento via cartão de crédito, é obrigatório que nesse campo seja incluído no parâmetro o retorno [da requisição de estorno de pagamento](https://developers.vnda.com.br/reference/post-api-v2-orders-order-code-chargeback). " + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Pedido cancelado", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + } + } + }, + "/api/v2/orders/{order_code}/items": { + "parameters": [ + { + "$ref": "#/components/parameters/Order.code" + }, + { + "$ref": "#/components/parameters/include_customizations_in_total" + } + ], + "get": { + "summary": "Lista os itens de um pedido", + "tags": [ + "Itens de pedido" + ], + "operationId": "get-api-v2-orders-items", + "responses": { + "200": { + "description": "Itens retornados", + "content": { + "application/json": { + "schema": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product.order" + } + } + } + } + } + }, + "404": { + "description": "Pedido não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "description": "Retorna os itens de um pedido pelo código do pedido" + } + }, + "/api/v2/orders/{order_code}/items/{item_id}/customizations": { + "parameters": [ + { + "schema": null, + "$ref": "#/components/parameters/Order.code" + }, + { + "schema": { + "type": "string" + }, + "name": "item_id", + "in": "path", + "description": "Código identificador do item", + "required": true + } + ], + "get": { + "summary": "Retorna personalizações de um item", + "operationId": "get-api-v2-orders-order_id-items-item_id-customizations", + "tags": [ + "Itens de pedido" + ], + "description": "Lista as personalizações de um item do pedido pelos códigos do item e do pedido", + "responses": { + "200": { + "description": "Personalizações listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Order_item_customization.v1" + } + }, + "examples": { + "Com pesonalizações": { + "value": [ + { + "id": 1, + "number": 1, + "group_name": "Color", + "sku": "A1", + "name": "Red", + "price": 0, + "intl_price": 0, + "handling_days": 0 + } + ] + }, + "Sem personalizações": { + "value": [] + } + } + } + } + }, + "404": { + "description": "Quando o item não é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + } + } + }, + "/api/v2/clients/{id_client}/orders": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "id_client", + "in": "path", + "required": true, + "description": "Código identificador do cliente" + } + ], + "get": { + "summary": "Lista os pedidos de um cliente", + "tags": [ + "Clientes" + ], + "operationId": "get-api-v2-clients-id-orders", + "responses": { + "200": { + "description": "Pedidos retornados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Order.v1" + } + } + } + } + }, + "404": { + "description": "Cliente não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "description": "Retorna os pedidos de um cliente pelo seu `id_client`" + } + }, + "/api/v2/orders/{order_code}/packages/{package_code}/invoices": { + "parameters": [ + { + "$ref": "#/components/parameters/Order.code" + }, + { + "$ref": "#/components/parameters/Package.code" + } + ], + "get": { + "summary": "Retorna notas fiscais de um pacote", + "tags": [ + "Notas fiscais de pedidos" + ], + "responses": { + "200": { + "description": "Notas fiscais listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Invoice.v1" + } + } + } + } + }, + "404": { + "description": "Pedido ou pacote não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "operationId": "get-api-v2-orders-order-code-packages-package_code-invoices", + "description": "Retorna as notas fisicais de um pacote do pedido" + }, + "post": { + "summary": "Inclui nota fiscal em um pedido", + "operationId": "post-api-v2-orders-order-code-packages-package_code-invoices", + "responses": { + "201": { + "description": "Nota fiscal adicionada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invoice.v1" + } + } + } + }, + "422": { + "description": "Parâmetros enviados inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Notas fiscais de pedidos" + ], + "description": "Inclui nota fiscal no pacote de um pedido", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invoice.v1" + } + } + } + } + } + }, + "/api/v2/orders/{order_code}/packages/{package_code}/invoices/{number}": { + "parameters": [ + { + "schema": null, + "$ref": "#/components/parameters/Order.code" + }, + { + "schema": null, + "$ref": "#/components/parameters/Package.code" + }, + { + "schema": { + "type": "string" + }, + "name": "number", + "in": "path", + "required": true, + "description": "Número da nota fiscal" + } + ], + "patch": { + "summary": "Atualiza uma nota fiscal", + "operationId": "patch-api-v2-orders-order-code-packages-package_code-invoices-number", + "responses": { + "204": { + "description": "Nota fiscal atualizada" + }, + "404": { + "description": "Pedido ou nota fiscal não encontrada", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + }, + "422": { + "description": "Parâmetros enviados inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + } + } + }, + "tags": [ + "Notas fiscais de pedidos" + ], + "description": "Atualiza uma nota fiscal", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Invoice.v1" + } + } + } + } + }, + "delete": { + "summary": "Remove uma nota fiscal", + "operationId": "delete-api-v2-orders-order-code-packages-package_code-invoices-number", + "responses": { + "204": { + "description": "Nota fiscal removida" + }, + "404": { + "description": "Nota fiscal não encontrada" + } + }, + "tags": [ + "Notas fiscais de pedidos" + ], + "description": "Remove uma nota fiscal" + } + }, + "/api/feed/orders": { + "parameters": [], + "get": { + "summary": "Lista os pedidos do feed", + "tags": [ + "Order Feed" + ], + "operationId": "get-feed-orders", + "description": "Permite listar os pedidos pendentes do feed", + "parameters": [ + { + "schema": { + "type": "boolean", + "enum": [ + true + ] + }, + "in": "query", + "name": "include_shipping_address", + "allowEmptyValue": true, + "description": "Selecione `true` para incluir o endereço na resposta" + }, + { + "schema": { + "type": "string", + "enum": [ + "received", + "confirmed", + "canceled" + ] + }, + "in": "query", + "name": "status", + "description": "Filtra os pedidos por status" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/Orders" + }, + "404": { + "description": "Domínio de loja não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + } + }, + "post": { + "summary": "Marca os pedidos do feed", + "operationId": "post-api-feed-orders", + "responses": { + "204": { + "description": "No Content" + }, + "404": { + "description": "Domínio de loja não encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/404.v1" + } + } + } + } + }, + "tags": [ + "Order Feed" + ], + "description": "Permite marcar os pedidos para que eles sejam filtrados da listagem do feed", + "requestBody": { + "$ref": "#/components/requestBodies/Orders" + } + } + } + }, + "components": { + "schemas": { + "Banner": { + "title": "Banner", + "type": "object", + "description": "Modelo que representa um banner na API", + "properties": { + "big_thumb": { + "type": "string" + }, + "color": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "end_at": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "external": { + "type": "boolean" + }, + "file_name": { + "type": "string" + }, + "file_uid": { + "type": "string" + }, + "html_description": { + "type": "string", + "nullable": true + }, + "id": { + "type": "integer" + }, + "norder": { + "type": "integer", + "nullable": true + }, + "plain_description": { + "type": "string", + "nullable": true + }, + "small_thumb": { + "type": "string" + }, + "start_at": { + "type": "string", + "format": "date-time" + }, + "subtitle": { + "type": "string", + "nullable": true + }, + "tag": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "url": { + "type": "string", + "nullable": true, + "format": "uri" + } + }, + "required": [ + "big_thumb", + "color", + "description", + "end_at", + "external", + "file_name", + "file_uid", + "html_description", + "id", + "norder", + "plain_description", + "small_thumb", + "start_at", + "subtitle", + "tag", + "title", + "updated_at", + "url" + ] + }, + "SlimBanner": { + "title": "SlimBanner", + "type": "object", + "description": "Modelo que representa um banner simplificado na API", + "properties": { + "id": { + "type": "integer" + }, + "tag": { + "type": "string" + }, + "title": { + "type": "string" + }, + "subtitle": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "url": { + "type": "string", + "nullable": true + }, + "external": { + "type": "boolean" + }, + "start_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "end_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "file_url": { + "type": "string", + "nullable": true + }, + "norder": { + "type": "integer", + "nullable": true + }, + "color": { + "type": "string", + "nullable": true + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "tag", + "title", + "subtitle", + "description", + "url", + "external", + "start_at", + "end_at", + "file_url", + "norder", + "color", + "updated_at" + ] + }, + "Variant": { + "title": "Variant", + "type": "object", + "description": "Modelo que representa uma variante na API", + "properties": { + "id": { + "type": "integer", + "description": "Código identificador da variante" + }, + "main": { + "type": "boolean", + "description": "Identifica se é a variante principal do produto. Para `true` a variante é principal e `false` a variante é secundária" + }, + "available": { + "type": "boolean", + "description": "Identifica se a variante está ativa em `true` e desativa em `false`" + }, + "sku": { + "type": "string", + "description": "Código SKU da variante" + }, + "name": { + "type": "string", + "description": "Nome da variante" + }, + "slug": { + "type": "string", + "description": "Slug da URL da variante" + }, + "min_quantity": { + "type": "integer", + "description": "Quantidade mínima para venda" + }, + "quantity": { + "type": "integer", + "description": "Quantidade física" + }, + "quantity_sold": { + "type": "integer", + "description": "" + }, + "stock": { + "type": "integer", + "description": "Quantidade disponível" + }, + "custom_attributes": { + "type": "object", + "description": "Customização da variante" + }, + "properties": { + "type": "object", + "properties": { + "property1": { + "$ref": "#/components/schemas/Variant_property.v1" + }, + "property2": { + "$ref": "#/components/schemas/Variant_property.v1" + }, + "property3": { + "$ref": "#/components/schemas/Variant_property.v1" + } + }, + "description": "[Atributos](https://developers.vnda.com.br/docs/atributos-de-produto) da variante" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Data e horário da última atualização da variante" + }, + "price": { + "type": "number", + "description": "Preço do item" + }, + "installments": { + "type": "array", + "items": { + "type": "number" + }, + "description": "Relação das parcelas para pagamento do item parcelado" + }, + "available_quantity": { + "type": "integer", + "description": "Unidades reservadas e não reservadas do item" + }, + "weight": { + "type": "number", + "description": "Massa do produto, em gramas" + }, + "width": { + "type": "number", + "description": "Largura do produto, em centímetros" + }, + "height": { + "type": "number", + "description": "Altura do produto, em centímetros" + }, + "length": { + "type": "number", + "description": "Comprimento do produito, em centímetros" + }, + "handling_days": { + "type": "integer", + "description": "Dias de manuseio da variante" + }, + "inventories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Variant_inventory.v1" + }, + "description": "Relação de itens por estoque (armazém)" + }, + "sale_price": { + "type": "number", + "description": "Preço promocional" + }, + "intl_price": { + "type": "number", + "description": "Preço internacional" + }, + "image_url": { + "type": "string", + "description": "URL da imagem da variante" + }, + "product_id": { + "type": "integer", + "description": "Código identificador `ID` do produto" + }, + "barcode": { + "type": "string", + "nullable": true, + "description": "Código de barra da variante" + }, + "norder": { + "type": "integer" + } + }, + "x-examples": { + "example-1": { + "id": 27, + "main": false, + "available": true, + "sku": "13001", + "name": "Tamanho: PP | Cor: Branca", + "slug": "camiseta", + "min_quantity": 1, + "quantity": 85, + "stock": 83, + "custom_attributes": { + "size": "PP", + "color": "#FFFFFF" + }, + "properties": {}, + "updated_at": "2019-08-01T18:36:52.718-03:00", + "price": 169.9, + "installments": [ + 169.9 + ], + "available_quantity": 83, + "weight": 0.1, + "width": 11, + "height": 2, + "length": 16, + "handling_days": 0, + "inventories": [], + "sale_price": 169.9, + "intl_price": 33.98, + "image_url": "//b0.vnda.com.br/x120/shop/2014/07/08/camiseta.jpg", + "product_id": 6, + "barcode": null, + "norder": 1 + } + } + }, + "ProductImage": { + "title": "ProductImage", + "type": "object", + "description": "Modelo que representa uma imagem de um produto", + "properties": { + "id": { + "type": "integer", + "description": "Código identificador `ID` da imagem" + }, + "url": { + "type": "string", + "description": "URL da imagem" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Data e horário da última atualização da imagem do produto" + }, + "variant_ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Códigos das variantes que utilizam a imagem" + } + }, + "required": [ + "id", + "url", + "updated_at", + "variant_ids" + ] + }, + "ProductSearch": { + "title": "ProductSearch", + "type": "object", + "description": "Modelo que representa um produto retornado via busca no Elasticsearch", + "properties": { + "id": { + "type": "integer" + }, + "active": { + "type": "boolean" + }, + "available": { + "type": "boolean" + }, + "subscription": { + "type": "boolean" + }, + "slug": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "reference_lowercase": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "image_url": { + "type": "string", + "nullable": true + }, + "url": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "title", + "subtitle", + "description", + "importance", + "type", + "image_url" + ], + "properties": { + "name": { + "type": "string", + "pattern": "[a-z0-9\\-_]+" + }, + "title": { + "type": "string" + }, + "subtitle": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "importance": { + "type": "number", + "nullable": true + }, + "type": { + "type": "string" + }, + "image_url": { + "type": "string", + "nullable": true + } + } + } + }, + "price": { + "type": "number", + "description": "Preço do item" + }, + "on_sale": { + "type": "boolean" + }, + "sale_price": { + "type": "number", + "description": "Preço promocional" + }, + "intl_price": { + "type": "number" + }, + "discount_id": { + "type": "integer" + }, + "discount_rule": { + "type": "object", + "nullable": true, + "required": [ + "type", + "amount" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "fixed", + "percentage" + ] + }, + "amount": { + "type": "number" + } + } + }, + "discount": { + "type": "object", + "nullable": true, + "required": [ + "name", + "description", + "facebook", + "valid_to" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "facebook": { + "type": "boolean", + "description": "Em desuso", + "default": false + }, + "valid_to": { + "type": "string" + } + } + }, + "images": { + "type": "array", + "items": { + "type": "object", + "required": [ + "sku", + "url" + ], + "properties": { + "sku": { + "type": "string" + }, + "url": { + "nullable": true, + "type": "string" + } + } + } + }, + "variants": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VariantProductSearch" + } + }, + "installments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProductInstallment" + } + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Data e horário da última atualização do produto" + } + }, + "required": [ + "id", + "active", + "available", + "subscription", + "slug", + "reference", + "reference_lowercase", + "name", + "description", + "image_url", + "url", + "tags", + "price", + "on_sale", + "sale_price", + "intl_price", + "discount_id", + "discount_rule", + "discount", + "images", + "variants", + "installments", + "created_at", + "updated_at" + ] + }, + "VariantProductSearch": { + "title": "VariantProductSearch", + "type": "object", + "description": "Modelo que representa uma variante retornada via busca no Elasticsearch", + "properties": { + "id": { + "type": "integer", + "minimum": 1 + }, + "sku": { + "type": "string", + "minLength": 1 + }, + "sku_lowercase": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "nullable": true + }, + "full_name": { + "type": "string", + "minLength": 1 + }, + "main": { + "type": "boolean" + }, + "available": { + "type": "boolean" + }, + "image_url": { + "type": "string", + "nullable": true + }, + "price": { + "type": "number", + "description": "Preço do item" + }, + "sale_price": { + "type": "number", + "description": "Preço promocional" + }, + "intl_price": { + "type": "number" + }, + "installments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProductInstallment" + } + }, + "stock": { + "type": "integer", + "description": "Quantidade de itens disponíveis" + }, + "quantity": { + "type": "integer" + }, + "quantity_sold": { + "type": "integer", + "description": "Quantidade de itens vendidos" + }, + "min_quantity": { + "type": "integer", + "description": "Quantidade mínima para venda" + }, + "available_quantity": { + "type": "integer" + }, + "custom_attributes": { + "type": "object", + "nullable": true, + "description": "Customização da variante" + }, + "properties": { + "type": "object", + "properties": { + "property1": { + "$ref": "#/components/schemas/VariantPropertyProductSearch" + }, + "property2": { + "$ref": "#/components/schemas/VariantPropertyProductSearch" + }, + "property3": { + "$ref": "#/components/schemas/VariantPropertyProductSearch" + } + }, + "description": "[Atributos](https://developers.vnda.com.br/docs/atributos-de-produto) da variante" + }, + "inventories": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "slug", + "available", + "price", + "sale_price", + "quantity", + "quantity_sold", + "place" + ], + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "slug": { + "type": "string", + "minLength": 1 + }, + "available": { + "type": "boolean" + }, + "price": { + "type": "number", + "description": "Preço do item" + }, + "sale_price": { + "type": "number", + "description": "Preço promocional" + }, + "quantity": { + "type": "number", + "nullable": true + }, + "quantity_sold": { + "type": "number", + "description": "Quantidade de itens vendidos" + }, + "place": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "number" + }, + "name": { + "type": "string" + } + } + } + } + } + }, + "handling_days": { + "type": "integer", + "description": "Dias de manuseio da variante" + }, + "barcode": { + "type": "string", + "nullable": true + }, + "weight": { + "type": "number", + "description": "Massa do produto, em gramas" + }, + "width": { + "type": "number", + "description": "Largura do produto, em centímetros" + }, + "height": { + "type": "number", + "description": "Altura do produto, em centímetros" + }, + "length": { + "type": "number", + "description": "Comprimento do produito, em centímetros" + } + }, + "required": [ + "id", + "sku", + "sku_lowercase", + "name", + "full_name", + "main", + "available", + "image_url", + "price", + "sale_price", + "intl_price", + "installments", + "stock", + "quantity", + "quantity_sold", + "min_quantity", + "available_quantity", + "custom_attributes", + "properties", + "inventories", + "handling_days", + "barcode", + "weight", + "width", + "height", + "length" + ] + }, + "VariantPropertyProductSearch": { + "title": "VariantPropertyProductSearch", + "type": "object", + "description": "Modelo que representa uma propriedade de uma variante quando retornada via Elasticsearch", + "nullable": true, + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "defining": { + "type": "boolean" + } + }, + "required": [ + "name", + "value", + "defining" + ] + }, + "Cart": { + "type": "object", + "title": "Cart", + "description": "Modelo que representa um carrinho na API", + "additionalProperties": false, + "properties": { + "id": { + "type": "integer" + }, + "email": { + "type": "string", + "nullable": true + }, + "shipping_method": { + "type": "string", + "nullable": true + }, + "items_count": { + "type": "integer" + }, + "quotation_responses_count": { + "type": "integer" + }, + "payment_responses_count": { + "type": "integer" + }, + "has_payment_responses": { + "type": "boolean" + }, + "has_phone": { + "type": "boolean" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "email", + "shipping_method", + "items_count", + "quotation_responses_count", + "payment_responses_count", + "has_payment_responses", + "has_phone", + "updated_at" + ] + }, + "CartItemCustomization": { + "type": "object", + "title": "CartItemCustomization", + "description": "Modelo que representa uma personalização de item do carrinho na API", + "additionalProperties": false, + "properties": { + "id": { + "type": "integer", + "description": "Código identificador do produto" + }, + "group_name": { + "type": "string" + }, + "name": { + "type": "string" + }, + "number": { + "type": "integer" + }, + "price": { + "type": "number", + "description": "Preço unitário" + }, + "intl_price": { + "type": "number", + "description": "Preço internacional" + }, + "handling_days": { + "type": "integer", + "description": "Número de dias para manuseio" + }, + "sku": { + "type": "string", + "nullable": true, + "description": "Código SKU do produto" + } + }, + "required": [ + "id", + "group_name", + "name", + "number", + "price", + "intl_price", + "handling_days", + "sku" + ] + }, + "ProductInstallment": { + "title": "ProductInstallment", + "type": "object", + "properties": { + "number": { + "type": "integer" + }, + "price": { + "type": "number", + "description": "Preço do item" + }, + "interest": { + "type": "boolean" + }, + "interest_rate": { + "type": "number" + }, + "total": { + "type": "number" + } + }, + "required": [ + "number", + "price", + "interest", + "interest_rate", + "total" + ], + "description": "Modelo que representa uma parcela", + "x-internal": false + }, + "VariantProperty": { + "title": "VariantProperty", + "type": "object", + "description": "Modelo que representa uma propriedade de uma variante", + "properties": { + "name": { + "type": "string", + "description": "Nome da propriedade" + }, + "value": { + "type": "string", + "description": "Valor da propriedade" + }, + "defining": { + "type": "boolean", + "description": "Indica se a variante possui uma definição (`true`) ou se a variante não possui (`false`)" + } + }, + "required": [ + "name", + "value", + "defining" + ], + "example": { + "example-property1": { + "name": "Tamanho", + "value": "G", + "defining": true + }, + "example-property2": { + "name": "Cor", + "value": "Amarelo", + "defining": true + } + } + }, + "ProductPriceVariant": { + "title": "ProductPriceVariant", + "type": "object", + "description": "Modelo que representa os preços de uma variante", + "properties": { + "main": { + "type": "boolean", + "description": "Define se a variante do produto é a principal" + }, + "sku": { + "type": "string", + "description": "Código SKU da variante" + }, + "price": { + "type": "number", + "description": "Preço do item" + }, + "on_sale": { + "type": "boolean" + }, + "sale_price": { + "type": "number", + "description": "Preço promocional" + }, + "intl_price": { + "type": "number" + }, + "available": { + "type": "boolean" + }, + "properties": { + "type": "object", + "properties": { + "property1": { + "$ref": "#/components/schemas/VariantProperty" + }, + "property2": { + "$ref": "#/components/schemas/VariantProperty" + }, + "property3": { + "$ref": "#/components/schemas/VariantProperty" + } + }, + "description": "[Atributos](https://developers.vnda.com.br/docs/atributos-de-produto) da variante" + }, + "stock": { + "type": "number", + "description": "Quantidade de itens disponíveis" + }, + "installments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProductInstallment" + } + } + }, + "required": [ + "main", + "sku", + "price", + "on_sale", + "sale_price", + "intl_price", + "available", + "properties", + "stock", + "installments" + ], + "x-internal": false + }, + "Mapping": { + "type": "object", + "description": "Modelo que representa um mapeamento na API", + "properties": { + "id": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "from": { + "type": "array", + "items": { + "type": "string" + } + }, + "to": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "key" + ] + }, + "Package": { + "title": "Package", + "type": "object", + "description": "Modelo que representa um pacote na API", + "properties": { + "actual_shipping_method": { + "type": "string", + "nullable": true + }, + "code": { + "type": "string", + "minLength": 1, + "description": "Código identificador do pacote" + }, + "delivered_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Data de entrega do pacote" + }, + "delivered_email_sent_at": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "delivery_days": { + "type": "integer", + "minimum": 0, + "description": "Número de dias para entrega" + }, + "delivery_type": { + "type": "string", + "minLength": 1, + "description": "Tipo de envio do pacote" + }, + "delivery_work_days": { + "type": "integer", + "minimum": 0, + "description": "Quantidade de dias úteis para entrega" + }, + "fulfillment_company": { + "type": "string", + "nullable": true, + "description": "Transportadora" + }, + "fulfillment_status": { + "type": "string", + "minLength": 1, + "enum": [ + "waiting", + "shipped", + "delivered" + ], + "description": "Status de envio" + }, + "integrated": { + "type": "boolean", + "default": false + }, + "invoiced": { + "type": "boolean", + "default": false + }, + "label": { + "type": "string", + "minLength": 1 + }, + "properties": { + "type": "object" + }, + "quoted_shipping_price": { + "type": "number", + "minimum": 0 + }, + "shipped_at": { + "type": "string", + "format": "date-time" + }, + "shipped_email_sent_at": { + "type": "string", + "format": "date-time" + }, + "shipping_label": { + "type": "string" + }, + "shipping_name": { + "type": "string" + }, + "shipping_price": { + "type": "number" + }, + "total": { + "type": "number", + "minimum": 0 + }, + "tracked_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "Data e horário da última atualização do código de rastreio do pacote" + }, + "tracking_code": { + "type": "string", + "nullable": true, + "description": "Código de rastreio do pacote" + } + }, + "required": [ + "actual_shipping_method", + "code", + "delivered_at", + "delivered_email_sent_at", + "delivery_days", + "delivery_type", + "delivery_work_days", + "fulfillment_company", + "fulfillment_status", + "integrated", + "invoiced", + "label", + "properties", + "quoted_shipping_price", + "shipped_at", + "shipped_email_sent_at", + "shipping_label", + "shipping_name", + "shipping_price", + "total", + "tracked_at", + "tracking_code" + ] + }, + "404.v1": { + "title": "404", + "type": "object", + "x-examples": { + "Not found": { + "error": "not found" + } + }, + "description": "Modelo que representa uma mensagem de erro 404", + "properties": { + "error": { + "type": "string", + "enum": [ + "not found" + ] + } + }, + "required": [ + "error" + ] + }, + "422.v1": { + "title": "422", + "type": "object", + "properties": { + "errors": { + "type": "object", + "properties": { + "{field}": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "description": "Modelo que representa um erro de validação na criação e atualização de registros" + }, + "Address.v1": { + "title": "Address", + "type": "object", + "description": "Modelo que representa um endereço na API", + "properties": { + "id": { + "type": "integer" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "company_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "documents": { + "type": "object", + "properties": { + "cpf": { + "type": "string" + }, + "cnpj": { + "type": "string" + } + } + }, + "street_name": { + "type": "string" + }, + "street_number": { + "type": "string" + }, + "complement": { + "type": "string" + }, + "neighborhood": { + "type": "string" + }, + "first_phone_area": { + "type": "string" + }, + "first_phone": { + "type": "string" + }, + "second_phone_area": { + "type": "string" + }, + "second_phone": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "recipient_name": { + "type": "string" + } + } + }, + "Audience_member.v1": { + "title": "Audience Member", + "type": "object", + "description": "Modelo que representa um membro do público", + "properties": { + "id": { + "type": "integer" + }, + "first_name": { + "type": "string", + "nullable": true + }, + "last_name": { + "type": "string", + "nullable": true + }, + "email": { + "type": "string", + "format": "email" + }, + "phone_area": { + "type": "string", + "nullable": true + }, + "phone": { + "type": "string", + "nullable": true + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "email" + ] + }, + "Bonus.v1": { + "title": "Bonus", + "type": "object", + "description": "Modelo que representa um bônus na API", + "properties": { + "amount": { + "type": "number" + }, + "token": { + "type": "string" + }, + "valid_from": { + "type": "string", + "format": "date-time" + }, + "valid_thru": { + "type": "string", + "format": "date-time" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + }, + "Cart.v1": { + "title": "Cart", + "type": "object", + "description": "Modelo que representa um carrinho na API", + "properties": { + "agent": { + "type": "string", + "nullable": true, + "description": "Agente que criou o carrinho" + }, + "billing_address_id": { + "type": "integer", + "nullable": true, + "description": "Código identificador `ID` do endereço de cobrança do carrinho" + }, + "channel": { + "type": "string", + "nullable": true, + "description": "Canal de venda que originou o carrinho" + }, + "client_id": { + "type": "integer", + "nullable": true, + "description": "Código identificador `ID` do cliente" + }, + "code": { + "type": "string", + "description": "Código identificador `ID` do carrinho" + }, + "coupon_code": { + "type": "string", + "nullable": true, + "description": "Código de cupom de desconto utilizado no carrinho" + }, + "discount": { + "type": "object", + "nullable": true, + "required": [ + "id", + "name", + "description", + "facebook", + "valid_to", + "seal_uid", + "seal_url", + "start_at", + "end_at", + "email", + "cpf", + "tags" + ], + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "facebook": { + "type": "boolean", + "default": false + }, + "valid_to": { + "type": "string", + "enum": [ + "store", + "cart" + ] + }, + "seal_uid": { + "type": "string", + "description": "DEPRECATED" + }, + "seal_url": { + "type": "string", + "description": "DEPRECATED" + }, + "start_at": { + "type": "string", + "format": "date-time" + }, + "end_at": { + "type": "string", + "nullable": true, + "format": "date-time" + }, + "email": { + "type": "string", + "nullable": true + }, + "cpf": { + "type": "string", + "nullable": true + }, + "tags": { + "type": "string", + "nullable": true + } + }, + "$ref": "#/components/schemas/Discount.v1", + "description": "Promoção aplicada no carrinho" + }, + "discount_price": { + "type": "number", + "deprecated": true, + "description": "Valor do desconto" + }, + "extra": { + "type": "object", + "description": "Campo para registro de observações, chave ou valores necessários" + }, + "id": { + "type": "integer", + "description": "Código identificador `ID` do carrinho" + }, + "items": { + "type": "array", + "description": "Itens do carrinho", + "items": { + "$ref": "#/components/schemas/Cart_item.v1" + } + }, + "items_count": { + "type": "integer", + "description": "Unidades do item no carrinho" + }, + "shipping_address_id": { + "type": "integer", + "nullable": true, + "description": "Código identificador `ID` do endereço de entrega do carrinho" + }, + "shipping_method": { + "type": "string", + "nullable": true, + "description": "Método de envio selecionado para o carrinho, como por exemplo: normal, expressa e agendada." + }, + "shipping_methods": { + "type": "array", + "items": { + "type": "object", + "properties": { + "package": { + "type": "string" + }, + "name": { + "type": "string" + }, + "label": { + "type": "string" + }, + "price": { + "type": "string" + }, + "delivery_days": { + "type": "string" + }, + "delivery_type": { + "type": "string" + }, + "description": { + "type": "string" + }, + "short_description": { + "type": "string" + }, + "fulfillment_company": { + "type": "string", + "nullable": true + } + }, + "required": [ + "package", + "name", + "label", + "price", + "delivery_days", + "delivery_type", + "description", + "short_description", + "fulfillment_company" + ], + "$ref": "#/components/schemas/Shipping_methods.v1" + }, + "description": "Lista com as entregas disponíveis para os itens do carrinho de acordo com o endereço de envio" + }, + "shipping_price": { + "type": "number", + "nullable": true, + "description": "Preço de envio" + }, + "subtotal": { + "type": "number", + "description": "Valor da soma dos itens do carrinho, sem considerar descontos de cupom, carrinho e frete." + }, + "token": { + "type": "string", + "description": "Token do carrinho" + }, + "total": { + "type": "number", + "description": "Valor final do carrinho" + }, + "total_for_deposit": { + "type": "number", + "description": "Valor total do carrinho para pagamento por depósito" + }, + "total_for_slip": { + "type": "number", + "description": "Valor total do carrinho para pagamento por boleto" + }, + "total_for_pix": { + "type": "number", + "description": "Valor do carrinho para pagamento por PIX" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Data da última atualização do carrinho" + }, + "rebate_token": { + "type": "string", + "nullable": true, + "description": "Código identificador `ID` do desconto por bônus" + }, + "rebate_discount": { + "type": "number", + "description": "Desconto por bônus do cliente" + }, + "handling_days": { + "type": "number", + "description": "Número de dias para manuseio dos itens" + }, + "subtotal_discount": { + "type": "number", + "description": "Valor de desconto de promoções aplicadas ao subtotal do carrinho" + }, + "total_discount": { + "type": "number", + "description": "Valor de desconto de promoções aplicadas ao valor total do carrinho" + }, + "installments": { + "$ref": "#/components/schemas/Cart_installment.v1", + "description": "Parcelas para pagamento parcelado" + }, + "user_id": { + "type": "string", + "description": "Código identificador `ID` do cliente" + } + }, + "required": [ + "agent", + "billing_address_id", + "channel", + "client_id", + "code", + "coupon_code", + "discount", + "discount_price", + "extra", + "id", + "items", + "items_count", + "shipping_address_id", + "shipping_method", + "shipping_methods", + "shipping_price", + "subtotal", + "token", + "total", + "total_for_deposit", + "total_for_slip", + "total_for_pix", + "updated_at", + "rebate_token", + "rebate_discount", + "handling_days", + "subtotal_discount", + "total_discount" + ] + }, + "Cart_installment.v1": { + "title": "Cart Installment", + "type": "object", + "description": "Modelo que representa uma parcela do total de um carrinho", + "properties": { + "interest": { + "type": "boolean", + "description": "Identifica se há (`true`) ou não (`false`) juros no parcelamento" + }, + "interest_rate": { + "type": "number", + "description": "Taxa de juros do parcelamento" + }, + "number": { + "type": "integer", + "description": "Número de parcelas" + }, + "price": { + "type": "number", + "description": "Valor de cada parcela" + }, + "total": { + "type": "number", + "description": "Valor total das parcelas" + } + }, + "required": [ + "interest", + "interest_rate", + "number", + "price", + "total" + ], + "x-examples": { + "Primeira parcela": { + "interest": false, + "interest_rate": 0, + "number": 1, + "price": 837, + "total": 837 + }, + "Segunda parcela": { + "interest": false, + "interest_rate": 0, + "number": 2, + "price": 418.5, + "total": 837 + } + } + }, + "Cart_item.v1": { + "title": "Cart Item", + "type": "object", + "description": "Modelo que representa um item no carrinho na API", + "x-tags": [ + "Carrinhos" + ], + "properties": { + "available_quantity": { + "type": "integer", + "description": "Unidades disponíveis do produto" + }, + "delivery_days": { + "type": "integer", + "description": "Número de dias para a entrega" + }, + "extra": { + "type": "object", + "description": "Campo para registro de observações, chave ou valores necessários" + }, + "place_id": { + "type": "integer", + "nullable": true, + "description": "Código identificador do local do produto" + }, + "price": { + "type": "number", + "description": "Preço do produto" + }, + "intl_price": { + "type": "number", + "description": "Preço internacional" + }, + "product_id": { + "type": "integer", + "description": "Código identificador `ID` do produto" + }, + "product_name": { + "type": "string", + "description": "Nome do produto" + }, + "product_reference": { + "type": "string", + "description": "Código de referência do produto" + }, + "product_url": { + "type": "string", + "description": "URL do produto no e-commerce" + }, + "quantity": { + "type": "integer", + "description": "Unidades do produto no carrinho" + }, + "seller": { + "type": "string", + "nullable": true, + "description": "Identificador do seller" + }, + "seller_name": { + "type": "string", + "nullable": true, + "description": "Nome do seller" + }, + "subtotal": { + "type": "number", + "description": "Valor do produto sem descontos e promoções" + }, + "total": { + "type": "number", + "description": "Valor total do produto" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Data da última atualização do carrinho" + }, + "variant_attributes": { + "type": "object", + "description": "Atributos da variante" + }, + "variant_min_quantity": { + "type": "integer", + "description": "Quantidade miníma de variantes para compra" + }, + "variant_name": { + "type": "string", + "description": "Nome da variante" + }, + "variant_price": { + "type": "number", + "description": "Preço da variante" + }, + "variant_intl_price": { + "type": "number", + "description": "Preço internacional da variante" + }, + "variant_properties": { + "type": "object", + "$ref": "#/components/schemas/Variant" + }, + "variant_sku": { + "type": "string", + "description": "Código SKU da [Variante](https://developers.vnda.com.br/docs/cat%C3%A1logo-de-produtos#produto-atributo-e-variante)" + }, + "id": { + "type": "string", + "description": "Código identificador do item no carrinho" + }, + "product_type": { + "type": "string", + "description": "Tipo de produto" + }, + "image_url": { + "type": "string", + "nullable": true, + "description": "URL da imagem da variante" + } + }, + "required": [ + "available_quantity", + "delivery_days", + "extra", + "place_id", + "price", + "intl_price", + "product_id", + "product_name", + "product_reference", + "product_url", + "quantity", + "seller", + "seller_name", + "subtotal", + "total", + "updated_at", + "variant_attributes", + "variant_min_quantity", + "variant_name", + "variant_price", + "variant_intl_price", + "variant_properties", + "variant_sku" + ] + }, + "Client.v1": { + "title": "Client", + "type": "object", + "description": "Modelo que representa um cliente na API", + "properties": { + "id": { + "type": "integer" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "gender": { + "type": "string" + }, + "phone_area": { + "type": "string", + "pattern": "[0-9]+" + }, + "phone": { + "type": "string", + "pattern": "[0-9]+" + }, + "document_type": { + "type": "string", + "enum": [ + "CPF", + "CNPJ" + ] + }, + "document_number": { + "type": "string", + "description": "Número de documento cadastrado pelo cliente" + }, + "cpf": { + "type": "string", + "pattern": "[0-9]+" + }, + "cnpj": { + "type": "string", + "pattern": "[0-9]+" + }, + "ie": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "lists": { + "type": "array", + "items": { + "type": "string" + } + }, + "facebook_uid": { + "type": "string" + }, + "liked_facebook_page": { + "type": "boolean" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "birthdate": { + "type": "string", + "format": "date" + }, + "recent_address": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "first_name": { + "type": "string" + }, + "last_name": { + "type": "string" + }, + "company_name": { + "type": "string" + }, + "street_name": { + "type": "string" + }, + "street_number": { + "type": "string" + }, + "neighborhood": { + "type": "string" + }, + "complement": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "first_phone_area": { + "type": "string" + }, + "first_phone": { + "type": "string" + }, + "second_phone_area": { + "type": "string" + }, + "second_phone": { + "type": "string" + }, + "email": { + "type": "string" + }, + "documents": { + "type": "object", + "properties": { + "cpf": { + "type": "string" + }, + "cnpj": { + "type": "string" + } + } + } + } + } + }, + "auth_token": { + "type": "string" + }, + "last_confirmed_order_at": { + "type": "string", + "format": "date-time" + }, + "received_orders_count": { + "type": "integer" + }, + "confirmed_orders_count": { + "type": "integer" + }, + "canceled_orders_count": { + "type": "integer" + }, + "renew_password": { + "type": "boolean", + "default": false + } + } + }, + "ClientAddress.v1": { + "title": "Client", + "type": "object", + "description": "Modelo que representa os endereços cadastrados pelo cliente na API", + "properties": { + "id": { + "type": "integer" + }, + "street_name": { + "type": "string" + }, + "street_number": { + "type": "string" + }, + "complement": { + "type": "string" + }, + "neighborhood": { + "type": "string" + }, + "label": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "client_id": { + "type": "integer" + } + } + }, + "Coupon.v1": { + "title": "Coupon", + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "code": { + "type": "string" + }, + "uses_per_code": { + "type": "integer" + }, + "uses_per_user": { + "type": "integer" + }, + "referrer_email": { + "type": "string", + "format": "email" + }, + "user_id": { + "type": "integer" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "orders_count": { + "type": "integer" + } + }, + "description": "Modelo que representa um cupom de desconto" + }, + "Customization.v1": { + "title": "Customization", + "type": "object", + "description": "Modelo que representa uma customização", + "properties": { + "id": { + "type": "integer" + }, + "group_name": { + "type": "string" + }, + "name": { + "type": "string" + }, + "label": { + "type": "string" + }, + "image_uid": { + "type": "string" + }, + "image_name": { + "type": "string" + }, + "price": { + "type": "number" + }, + "intl_price": { + "type": "number" + }, + "quantity": { + "type": "integer" + }, + "handling_days": { + "type": "integer" + }, + "tag_id": { + "type": "integer" + }, + "sku": { + "type": "string" + }, + "pattern": { + "type": "string", + "nullable": true + } + } + }, + "Products_attributes.v1": { + "title": "Products Attributes", + "type": "object", + "description": "Modelo que representa um atributo customizado de produto", + "properties": { + "index": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "mandatory": { + "type": "boolean" + }, + "updated_at": { + "type": "string" + } + } + }, + "Discount.v1": { + "title": "Discount", + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Código identificador `ID` do desconto" + }, + "name": { + "type": "string", + "description": "Nome do desconto ou promoção" + }, + "description": { + "type": "string", + "description": "Descrição do desconto" + }, + "start_at": { + "type": "string", + "format": "date-time", + "description": "Data de início da regra do desconto" + }, + "end_at": { + "type": "string", + "format": "date-time", + "description": "Data de fim da regra do desconto" + }, + "enabled": { + "type": "boolean", + "default": true, + "description": "Indica se o desconto está habilitado (`true`) ou desabilitado (`false`)" + }, + "facebook": { + "type": "boolean", + "description": "Em desuso", + "default": false + }, + "valid_to": { + "type": "string", + "description": "Indica a regra da promoção: se o desconto é aplicado na vitrine ou no carrinho da loja " + }, + "email": { + "type": "string", + "format": "email", + "description": "Email do cliente, no caso de promoções direcionadas para clientes específicos" + }, + "cpf": { + "type": "string", + "pattern": "[0-9]{11}", + "description": "Cadastro de Pessoa Física (CPF) do cliente, no caso de promoções direcionadas para clientes específicos" + }, + "tags": { + "type": "string", + "description": "Tag de agrupamento de promoção" + } + }, + "required": [ + "name", + "start_at", + "enabled" + ], + "description": "Modelo que representa uma promoção na API" + }, + "Discount_rule.v1": { + "title": "Discount Rule", + "type": "object", + "description": "Modelo que representa uma regra de desconto na API", + "properties": { + "id": { + "type": "integer" + }, + "amount": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": false, + "exclusiveMaximum": false + }, + "type": { + "type": "string", + "enum": [ + "fixed", + "percentage" + ] + }, + "apply_to": { + "type": "string" + }, + "min_quantity": { + "type": "integer", + "default": 0 + }, + "product": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "reference": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "tag": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "combined_product": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "reference": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "min_subtotal": { + "type": "number" + }, + "shipping_method": { + "type": "string" + }, + "shipping_rule": { + "type": "string", + "enum": [ + "any", + "all" + ] + }, + "regions": { + "type": "array", + "items": { + "type": "string" + } + }, + "agent_tag": { + "type": "string" + }, + "channel": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "amount", + "type", + "apply_to", + "min_quantity", + "min_subtotal" + ] + }, + "Invoice.v1": { + "title": "Invoice", + "type": "object", + "description": "Modelo que representa uma nota fiscal na API", + "properties": { + "number": { + "type": "integer", + "description": "Número da nota fiscal" + }, + "series": { + "type": "integer", + "description": "Número de série da nota fiscal" + }, + "issued_at": { + "type": "string", + "format": "date-time", + "description": "Data e horário da criação da nota fiscal" + }, + "key": { + "type": "string", + "description": "Chave da nota fiscal" + }, + "volumes": { + "type": "integer" + } + }, + "required": [ + "number" + ] + }, + "Menu.v1": { + "title": "Menu", + "type": "object", + "description": "Modelo que representa um menu na API", + "properties": { + "id": { + "type": "integer" + }, + "label": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "external": { + "type": "boolean" + }, + "parent_id": { + "type": "integer" + }, + "tag_id": { + "type": "integer" + }, + "tag_name": { + "type": "string" + }, + "page_id": { + "type": "integer" + }, + "page_slug": { + "type": "string" + }, + "items_count": { + "type": "integer" + }, + "updated_at": { + "type": "string", + "format": "date" + }, + "tooltip": { + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Menu.v1" + } + }, + "image_url": { + "type": "string" + }, + "simple_url": { + "type": "string" + }, + "position": { + "type": "string" + }, + "norder": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, + "Menu_in_tree.v1": { + "title": "Menu (Tree)", + "type": "object", + "description": "Modelo que representa um menu na API quando retornado pela ação de menu em árvore", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "external": { + "type": "boolean" + }, + "url": { + "type": "string" + }, + "tag_id": { + "type": "integer" + }, + "page_id": { + "type": "integer" + }, + "items_count": { + "type": "integer" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Menu_in_tree.v1" + } + }, + "updated_at": { + "type": "string", + "format": "date" + }, + "tooltip": { + "type": "string" + }, + "image_url": { + "type": "string" + }, + "simple_url": { + "type": "string" + }, + "norder": { + "type": "integer" + } + } + }, + "Order.v1": { + "title": "Order", + "type": "object", + "description": "Modelo que representa um pedido na API", + "properties": { + "rebate_discount": { + "type": "number", + "minimum": 0, + "description": "Desconto por bônus do cliente" + }, + "rebate_token": { + "type": "string", + "nullable": true, + "description": "Código identificador `ID` do desconto por bônus" + }, + "user_id": { + "type": "integer", + "description": "Código identificador `ID` do cliente" + }, + "updated_at": { + "type": "string", + "description": "Data da última atualização do pedido" + }, + "tracking_code_list": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Lista com os códigos de rastreio dos pacotes do pedido" + }, + "tracking_code": { + "type": "string", + "description": "Código de rastreio do pacote" + }, + "total": { + "type": "number", + "minimum": 0, + "description": "Valor final do pedido" + }, + "token": { + "type": "string" + }, + "taxes": { + "type": "number", + "minimum": 0 + }, + "subtotal": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true, + "description": "Valor da soma dos itens do pedido, desconsiderando descontos e frete." + }, + "status": { + "type": "string", + "enum": [ + "received", + "confirmed", + "canceled" + ], + "description": "Status do pedido" + }, + "payment_due_date": { + "type": "string", + "format": "date" + }, + "slip_url": { + "type": "string" + }, + "slip_token": { + "type": "string" + }, + "slip_due_date": { + "type": "string", + "format": "date" + }, + "slip": { + "type": "boolean" + }, + "shipping_tracked_at": { + "type": "string", + "format": "date-time" + }, + "shipping_price": { + "type": "number" + }, + "shipping_label": { + "type": "string" + }, + "shipped_at": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Data e horário de envio do pedido" + }, + "received_at": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Data e horário de recebimento do pedido" + }, + "payment_tid": { + "type": "string", + "nullable": true + }, + "payment_method": { + "type": "string", + "description": "Método de pagamento do pedido" + }, + "payment_gateway": { + "type": "string", + "nullable": true + }, + "payment_authorization": { + "type": "string", + "nullable": true + }, + "paid_at": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Data e horário do pagamento do pedido" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "extra": { + "type": "object" + }, + "height": { + "type": "number" + }, + "id": { + "type": "integer" + }, + "length": { + "type": "number" + }, + "original_price": { + "type": "number" + }, + "package": { + "type": "string" + }, + "picture_url": { + "type": "string" + }, + "place_city": { + "type": "string", + "nullable": true + }, + "place_id": { + "type": "integer" + }, + "place_name": { + "type": "string", + "nullable": true + }, + "price": { + "type": "number" + }, + "product_id": { + "type": "integer" + }, + "product_name": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "reference": { + "type": "string" + }, + "sku": { + "type": "string" + }, + "total": { + "type": "number", + "minimum": 0 + }, + "variant_id": { + "type": "integer" + }, + "variant_name": { + "type": "string", + "nullable": true + }, + "weight": { + "type": "number" + }, + "width": { + "type": "number" + }, + "barcode": { + "type": "string" + } + }, + "required": [ + "extra", + "product_id", + "product_name", + "quantity", + "reference", + "sku", + "total", + "variant_id", + "variant_name", + "weight", + "width" + ], + "$ref": "#/components/schemas/Product.order" + } + }, + "installments": { + "type": "number", + "minimum": 1, + "description": "Parcelas do pagamento parcelado" + }, + "id": { + "type": "integer", + "description": "Código identificador do pedido" + }, + "extra": { + "type": "object", + "description": "Campo de observações do pedido" + }, + "expected_delivery_date": { + "type": "string", + "format": "date" + }, + "email": { + "type": "string", + "description": "Email do cliente" + }, + "discount_price": { + "type": "number", + "description": "Valor do desconto aplicado no pedido" + }, + "deposit": { + "type": "boolean" + }, + "delivery_type": { + "type": "string" + }, + "delivery_message": { + "type": "string" + }, + "delivery_days": { + "type": "integer", + "description": "Dias para entrega" + }, + "delivered_at": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Data de entrega do pedido" + }, + "coupon_code": { + "type": "string", + "nullable": true, + "description": "Código de cupom do pedido" + }, + "confirmed_at": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Data e horário de confirmação do pedido" + }, + "code": { + "type": "string", + "description": "Código do pedido" + }, + "client_id": { + "type": "integer", + "description": "Código identificador (`ID`) do cliente" + }, + "channel": { + "type": "string", + "enum": [ + "ecommerce", + "direct" + ], + "description": "Canal de venda que originou o pedido" + }, + "cart_id": { + "type": "integer", + "description": "Código identificador do carrinho que originou o pedido" + }, + "card_validity": { + "type": "string", + "nullable": true, + "description": "Data de validade do cartão de crédito" + }, + "card_number": { + "type": "string", + "pattern": "^[*]{10,12}[0-9]{4}$", + "description": "Número do cartão de crédito" + }, + "card": { + "type": "boolean", + "description": "Retorna `true` se o método de pagamento do pedido é por cartão de crédito." + }, + "canceled_at": { + "type": "string", + "nullable": true, + "format": "date-time", + "description": "Data e horário do cancelamento do pedido" + }, + "browser_ip": { + "type": "string", + "format": "ipv4", + "description": "Endereço IP de origem do pedido" + }, + "agent": { + "type": "string", + "nullable": true, + "description": "Agente do pedido" + }, + "affiliate_tag": { + "type": "string", + "nullable": true + }, + "pix_qr_code": { + "type": "string", + "nullable": true + }, + "payment_authorization_code": { + "type": "string", + "nullable": true, + "description": "Código de autorização do pagamento" + }, + "bonus_granted": { + "type": "boolean", + "description": "Indica se o pedido gerou bônus" + }, + "has_split": { + "type": "boolean" + }, + "pix": { + "type": "boolean", + "description": "Indica se o pedido foi pago usando o Pix" + }, + "ame_qr_code": { + "type": "string", + "nullable": true + }, + "ame": { + "type": "boolean", + "description": "Indica se o pedido foi pago usando o Ame" + }, + "antifraud_assurance": { + "type": "string", + "nullable": true + } + }, + "required": [ + "rebate_discount", + "updated_at", + "total", + "token", + "taxes", + "subtotal", + "status", + "slip", + "shipped_at", + "received_at", + "payment_method", + "payment_gateway", + "payment_authorization", + "paid_at", + "email", + "discount_price", + "deposit", + "delivered_at", + "coupon_code", + "confirmed_at", + "code", + "client_id", + "channel", + "cart_id", + "card_validity", + "card_number", + "card", + "browser_ip", + "pix", + "ame" + ] + }, + "Order_item_customization.v1": { + "title": "Order Item Customization", + "type": "object", + "description": "Modelo que representa uma personalização de item do pedido na API", + "properties": { + "id": { + "type": "integer", + "description": "Código identificador `ID` da personalização" + }, + "number": { + "type": "integer", + "minimum": 1, + "description": "Número de tipos diferentes de personalizações em produtos do pedido" + }, + "group_name": { + "type": "string", + "description": "Grupo em que se enquadra a personalização" + }, + "name": { + "type": "string", + "description": "Nome do produto" + }, + "price": { + "type": "number", + "description": "Preço do produto" + }, + "intl_price": { + "type": "number", + "description": "Preço internacional" + }, + "handling_days": { + "type": "integer", + "description": "Dias de manuseio do produto" + }, + "sku": { + "type": "string", + "nullable": true, + "description": "Código SKU da variante de produto" + } + }, + "required": [ + "id", + "number", + "group_name", + "name", + "price", + "intl_price", + "handling_days", + "sku" + ] + }, + "Payables.v1": { + "title": "Recebíveis do usuário", + "description": "Valores que o usuário possui a receber", + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "status": { + "type": "string" + }, + "amount": { + "type": "number" + }, + "fee": { + "type": "number" + }, + "installment": { + "type": "number" + }, + "credit_date": { + "type": "string", + "format": "date-time" + }, + "order_date": { + "type": "string", + "format": "date-time" + }, + "transaction_id": { + "type": "number" + } + } + }, + "Payment_recipient.v1": { + "title": "Payment Recipient", + "type": "object", + "description": "Modelo que representa um recebedor na API", + "properties": { + "id": { + "type": "integer" + }, + "percentage": { + "type": "number", + "minimum": 0, + "maximum": 100 + }, + "active": { + "type": "boolean", + "default": true + }, + "charge_processing_fee": { + "type": "boolean", + "default": false + }, + "liable": { + "type": "boolean", + "default": false + }, + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "tag_name": { + "type": "string" + }, + "place_id": { + "type": "integer" + }, + "recipient_id": { + "type": "integer" + }, + "tag_id": { + "type": "integer" + }, + "user_id": { + "type": "integer" + }, + "include_shipping": { + "type": "boolean", + "default": true, + "description": "Indica se o frete deve ser incluído no split do pagamento" + } + }, + "required": [ + "id", + "percentage", + "recipient_id" + ] + }, + "Place.v1": { + "title": "Place", + "type": "object", + "description": "Modelo que representa um local na API", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "address_line_1": { + "type": "string", + "maxLength": 80 + }, + "address_line_2": { + "type": "string", + "maxLength": 80 + }, + "city": { + "type": "string", + "maxLength": 80 + }, + "neighborhood": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "home_page": { + "type": "string" + }, + "latitude": { + "type": "number" + }, + "longitude": { + "type": "number" + }, + "images": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": { + "type": "string", + "maxLength": 200 + }, + "email": { + "type": "string" + }, + "first_phone": { + "type": "string" + }, + "second_phone": { + "type": "string" + }, + "mobile_phone": { + "type": "string" + }, + "only_cash": { + "type": "boolean", + "default": false + }, + "categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "marker_url": { + "type": "string" + }, + "state": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "opening_hours": { + "type": "string" + }, + "warehouse": { + "type": "boolean", + "default": false + }, + "legal_name": { + "type": "string" + }, + "cnpj": { + "type": "string" + } + }, + "required": [ + "name", + "address_line_1", + "city", + "email" + ] + }, + "Product.v1": { + "title": "Product", + "type": "object", + "description": "Modelo que representa um produto na API", + "properties": { + "id": { + "type": "integer", + "description": "Código identificador `ID` do priduto" + }, + "active": { + "type": "boolean", + "description": "Indica se o produto está ativo (`true`) ou invativo (`false`)" + }, + "available": { + "type": "boolean", + "description": "Indica se o produto está disponível (`true`) ou indisponível (`false`)" + }, + "category_tags": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tag_type": { + "type": "string", + "description": "Tipo de tag" + }, + "name": { + "type": "string", + "description": "Nome da tag" + }, + "title": { + "type": "string", + "description": "Título da tag" + } + } + }, + "example": [ + { + "tag_type": "flag", + "name": "tag-veganos", + "title": "Veg" + }, + { + "tag_type": "flag", + "name": "liquida10", + "title": "10OFF" + } + ] + }, + "description": { + "type": "string", + "description": "Descrição do produto" + }, + "discount_id": { + "type": "integer", + "description": "Código de desconto" + }, + "html_description": { + "type": "string", + "description": "Descrição do produto em HTML" + }, + "image_url": { + "type": "string", + "description": "URL da imagem do produto" + }, + "installments": { + "type": "array", + "items": { + "type": "number" + }, + "description": "Relação das parcelas para pagamento parcelado" + }, + "min_quantity": { + "type": "string", + "description": "Quantidade mínima para venda do produto" + }, + "name": { + "type": "string", + "description": "Nome do produto" + }, + "on_sale": { + "type": "boolean", + "description": "Indica se o produto está em promoção (`true`) ou não (`false`)" + }, + "plain_description": { + "type": "string", + "description": "Descrição simplificada" + }, + "price": { + "type": "number", + "description": "Preço do item" + }, + "rating": { + "type": "object", + "properties": { + "rating": { + "type": "integer" + }, + "votes": { + "type": "integer" + } + }, + "description": "Média de avaliação do produto" + }, + "reference": { + "type": "string", + "description": "Código de referência do produto" + }, + "sale_price": { + "type": "number", + "description": "Preço promocional" + }, + "slug": { + "type": "string", + "description": "slug do produto" + }, + "tag_names": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Lista de tags que o produto é associado" + }, + "updated_at": { + "type": "string", + "description": "Data e horário da última atualização do produto" + }, + "url": { + "type": "string", + "description": "URL do produto" + }, + "variants": { + "type": "array", + "items": { + "type": "object", + "properties": { + "{id}": { + "$ref": "#/components/schemas/Product_variant.v1" + } + } + }, + "description": "Variantes do produto" + }, + "discount_rule": { + "type": "object", + "nullable": true, + "required": [ + "type", + "amount" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "fixed", + "percentage" + ] + }, + "amount": { + "type": "number" + } + }, + "description": "Regras de desconto de uma promoção" + }, + "images": { + "type": "array", + "description": "Imagens do produto", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "id do produto" + }, + "url": { + "type": "string", + "description": "Url do produto" + }, + "updated_at": { + "type": "string", + "description": "Data e horário da última atualização do produto" + }, + "variant_ids": { + "type": "array", + "items": { + "type": "object" + } + } + } + }, + "example": [ + { + "id": 0, + "url": "https://b0.vnda.com.br/product.gif?v=1514479363", + "updated_at": "2017-12-28T14:42:43.000-02:00", + "variant_ids": [ + 0 + ] + } + ] + } + }, + "required": [ + "discount_rule" + ] + }, + "Product_variant.v1": { + "description": "Modelo que representa uma variante na API", + "type": "object", + "properties": { + "available": { + "type": "boolean" + }, + "available_quantity": { + "type": "integer" + }, + "custom_attributes": { + "type": "object", + "description": "Customização da variante" + }, + "handling_days": { + "type": "integer", + "description": "Dias de manuseio da variante" + }, + "height": { + "type": "number" + }, + "id": { + "type": "integer", + "minimum": 1 + }, + "image_url": { + "type": "string", + "minLength": 1, + "description": "URL da imagem da variante" + }, + "installments": { + "type": "array", + "items": { + "type": "number" + } + }, + "inventories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Product_variant_inventory.v1" + } + }, + "length": { + "type": "number" + }, + "main": { + "type": "boolean" + }, + "min_quantity": { + "type": "integer", + "description": "Quantidade mínima para venda" + }, + "name": { + "type": "string", + "minLength": 1, + "description": "Nome da variante" + }, + "norder": { + "type": "integer" + }, + "price": { + "type": "number", + "description": "Preço do item" + }, + "product_id": { + "type": "integer" + }, + "properties": { + "type": "object", + "properties": { + "property1": { + "$ref": "#/components/schemas/Variant_property.v1" + }, + "property2": { + "$ref": "#/components/schemas/Variant_property.v1" + }, + "property3": { + "$ref": "#/components/schemas/Variant_property.v1" + } + }, + "description": "[Atributos](https://developers.vnda.com.br/docs/atributos-de-produto) da variante" + }, + "quantity": { + "type": "integer" + }, + "quantity_sold": { + "type": "integer", + "description": "Quantidade de itens vendidos" + }, + "sale_price": { + "type": "number", + "description": "Preço promocional" + }, + "sku": { + "type": "string", + "minLength": 1 + }, + "slug": { + "type": "string", + "minLength": 1 + }, + "stock": { + "type": "integer", + "description": "Quantidade de itens disponíveis" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Data e horário da última atualização da variante" + }, + "weight": { + "type": "number", + "description": "Massa do produto, em gramas" + }, + "width": { + "type": "number", + "description": "Largura do produto, em centímetros" + } + }, + "required": [ + "available", + "available_quantity", + "custom_attributes", + "handling_days", + "height", + "image_url", + "installments", + "length", + "main", + "min_quantity", + "name", + "norder", + "price", + "product_id", + "properties", + "quantity", + "sale_price", + "sku", + "slug", + "stock", + "updated_at", + "weight", + "width" + ] + }, + "Product_variant_inventory.v1": { + "description": "Modelo que representa um inventory da variante na API", + "type": "object", + "properties": { + "id": { + "type": "integer", + "description": "Código identificador `ID` do inventário" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Nome do inventário" + }, + "place_id": { + "type": "integer", + "description": "Código identificador do local" + }, + "place_name": { + "type": "string", + "nullable": true, + "description": "Nome do local" + }, + "price": { + "type": "number", + "description": "Preço do item" + }, + "quantity": { + "type": "integer", + "description": "Quantidade de itens no inventário" + }, + "quantity_sold": { + "type": "integer", + "description": "Quantidade de itens vendidos" + }, + "sale_price": { + "type": "number", + "description": "Preço promocional" + }, + "slug": { + "type": "string", + "minLength": 1, + "description": "Slug do inventário" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Data e horário da última atualização da variante no inventário" + }, + "variant_id": { + "type": "integer", + "description": "Código da variante" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Data de criação do inventário" + } + }, + "required": [ + "created_at", + "id", + "place_id", + "price", + "quantity", + "quantity_sold", + "sale_price", + "slug", + "updated_at", + "variant_id" + ], + "example": { + "inventories": { + "id": 524, + "slug": "normandia", + "price": 132, + "sale_price": 0, + "quantity": 500, + "quantity_sold": 0, + "name": null, + "variant_id": 532, + "updated_at": "2022-12-15T10:44:36.456-03:00", + "place_id": 3 + } + } + }, + "Shipping_methods.v1": { + "title": "Shipping Methods", + "type": "object", + "description": "Modelo que representa as formas de entrega na API", + "x-examples": { + "Forma de entrega": { + "name": "Normal", + "value": "pac", + "price": 1.99, + "description": "Prazo de até 7 dias corridos para a entrega do pedido", + "delivery_days": 7, + "value_needed_to_discount": null, + "shipping_method_id": 423, + "notice": null + } + }, + "properties": { + "name": { + "type": "string", + "description": "Nome do tipo de entrega, como por exemplo Normal, Expressa e Agendada" + }, + "value": { + "type": "string", + "description": "Identificador do método de envio" + }, + "price": { + "type": "number", + "description": "Preço de envio" + }, + "description": { + "type": "string", + "description": "Descrição do tipo de envio e prazo" + }, + "delivery_days": { + "type": "integer", + "description": "Número em dias do prazo de envio" + }, + "value_needed_to_discount": { + "type": "number", + "nullable": true, + "description": "Valor restante da compra para que o carrinho fique elegível para frete grátis" + }, + "shipping_method_id": { + "type": "integer", + "description": "Código identificador `ID` do tipo de envio" + }, + "notice": { + "type": "string", + "nullable": true, + "description": "Mensagem ou observação sobre a forma de envio" + }, + "fulfillment_company": { + "type": "string", + "nullable": true, + "description": "Empresa responsável pelo envio" + } + }, + "required": [ + "name", + "value", + "price", + "description", + "delivery_days", + "shipping_method_id", + "fulfillment_company" + ] + }, + "Order_items.v1": { + "title": "Order Items", + "type": "object", + "description": "Modelo que representa a lista de itens do pedido", + "properties": { + "id": { + "type": "integer" + }, + "variant_id": { + "type": "integer" + }, + "product_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "price": { + "type": "number" + }, + "weight": { + "type": "number" + }, + "width": { + "type": "number" + }, + "height": { + "type": "number" + }, + "length": { + "type": "number" + }, + "extra": { + "type": "object", + "properties": { + "customization": { + "type": "string" + } + } + }, + "picture_url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "sku": { + "type": "string" + }, + "product_name": { + "type": "string" + }, + "variant_name": { + "type": "string" + }, + "original_price": { + "type": "string" + }, + "place_id": { + "type": "string" + }, + "place_name": { + "type": "number" + }, + "place_city": { + "type": "number" + }, + "total": { + "type": "integer" + }, + "package": { + "type": "number" + }, + "has_customizations": { + "type": "integer" + }, + "barcode": { + "type": "integer" + } + } + }, + "Shop_asset.v1": { + "title": "Shop Asset", + "type": "object", + "description": "Modelo que representa as imagens da loja na API", + "properties": { + "id": { + "type": "integer" + }, + "position": { + "type": "string" + }, + "file_uid": { + "type": "string" + }, + "file_name": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "Shop_property.v1": { + "title": "Shop Property", + "type": "object", + "description": "Modelo que representa o atributo dos produtos da loja na API", + "properties": { + "name": { + "type": "string" + }, + "defining": { + "type": "boolean" + } + } + }, + "Shop_properties.v1": { + "title": "Shop Properties", + "type": "object", + "description": "Modelo que representa os atributos dos produtos da loja na API", + "properties": { + "property1": { + "$ref": "#/components/schemas/Shop_property.v1" + }, + "property2": { + "$ref": "#/components/schemas/Shop_property.v1" + }, + "property3": { + "$ref": "#/components/schemas/Shop_property.v1" + } + } + }, + "Site_message.v1": { + "title": "Site Message", + "type": "object", + "description": "Modelo que representa as mensagens do site na API", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "call_to_action": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date" + }, + "updated_at": { + "type": "string", + "format": "date" + } + } + }, + "Tag.v1": { + "title": "Tag", + "type": "object", + "description": "Modelo que representa uma tag na API", + "properties": { + "name": { + "type": "string", + "pattern": "[a-z0-9\\-_]+" + }, + "title": { + "type": "string" + }, + "subtitle": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "type": { + "type": "string" + }, + "products_count": { + "type": "integer" + }, + "image_url": { + "type": "string", + "nullable": true + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "name" + ] + }, + "Template.v1": { + "title": "Template", + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "body": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "path", + "updated_at" + ], + "description": "Modelo que representa um template na API" + }, + "User.v1": { + "title": "User", + "type": "object", + "description": "Modelo que representa um usuário na API", + "properties": { + "id": { + "type": "integer", + "description": "Código identificador do usuário" + }, + "email": { + "type": "string", + "format": "email", + "description": "Email do usuário" + }, + "access_token": { + "type": "string", + "description": "Token de validação de usuário logado (`access_token`)\n \nO `access_token` é gerado quando o usuário loga no Admin" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Nome do usuário" + }, + "admin": { + "type": "boolean", + "description": "Identificador de usuários administradores\n\nEsse atributo retorna `true` para um usuário administrador do ambiente de loja" + }, + "renew_password": { + "type": "boolean", + "description": "Identificador de usuários que atualizaram a senha inicial\n\nEsse atributo retorna `true` para um usuário que já redefiniu sua senha pelo menos uma vez" + }, + "role": { + "type": "integer", + "description": "Código da função do usuário na loja:\n\n - Agente: `0`;\n - Gestor: `1`;\n - Local: `2`;\n - Agente Social Selling: `3`." + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Tags para agrupamento de usuários\nAs tags podem ser são utilizadas para direcionar promoções para determinados usuários, organizar os recebedores em uma divisão de pagamentos, definir regras de comissão" + }, + "external_code": { + "type": "string", + "nullable": true, + "description": "Código externo do Vendedor. Esse campo é destinado para cadastrar um código de vendedor já existente em outro sistema." + }, + "phone_area": { + "type": "string", + "maxLength": 2, + "minLength": 2, + "description": "Código de Discagem Direta a Distância (DDD) do telefone do usuário" + }, + "phone": { + "type": "string", + "maxLength": 9, + "minLength": 8, + "description": "Número de telefone do usuário" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "Data de inclusão do usuário no Admin" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Data de atualização das informações do usuário" + } + }, + "required": [ + "email" + ] + }, + "Variant_inventory.v1": { + "description": "Model que representa um inventory da variante", + "type": "object", + "properties": { + "id": { + "type": "integer", + "minimum": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "place_id": { + "type": "integer" + }, + "price": { + "type": "number", + "description": "Preço do item" + }, + "quantity": { + "type": "integer" + }, + "quantity_sold": { + "type": "integer", + "description": "Quantidade de itens vendidos" + }, + "sale_price": { + "type": "number", + "description": "Preço promocional" + }, + "slug": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "place_id", + "price", + "quantity", + "quantity_sold", + "sale_price", + "slug" + ] + }, + "Variant_property.v1": { + "title": "variant_property", + "type": "object", + "properties": { + "defining": { + "type": "boolean", + "description": "Indica se a variante possui uma definição (`true`) ou se a variante não possui (`false`)" + }, + "name": { + "type": "string", + "description": "Nome da propriedade" + }, + "value": { + "type": "string", + "description": "Valor da propriedade" + } + }, + "required": [ + "defining", + "name" + ], + "description": "Modelo que representa uma propriedade customizada na API", + "example": { + "example-property1": { + "name": "Tamanho", + "value": "G", + "defining": true + }, + "example-property2": { + "name": "Cor", + "value": "Amarelo", + "defining": true + } + } + }, + "User.v0": { + "title": "Campos de cadastro de usuário", + "type": "object", + "description": "Modelo que representa o cadastro de usuário", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Email do usuário" + }, + "name": { + "type": "string", + "nullable": true, + "description": "Nome do usuário" + }, + "role_name": { + "type": "string", + "enum": [ + "Agente", + "Gestor", + "Local", + "Agente Social Selling" + ], + "description": "Função do usuário na loja" + }, + "password": { + "type": "string", + "description": "Senha do usuário" + }, + "password_confirmation": { + "type": "string", + "description": "Confirmação de senha do usuário" + }, + "external_code": { + "type": "string", + "description": "Código externo do Vendedor. Esse campo é destinado para cadastrar um código de vendedor já existente em outro sistema." + }, + "phone_area": { + "type": "string", + "maxLength": 2, + "minLength": 2, + "description": "Código de Discagem Direta a Distância (DDD) do telefone do usuário" + }, + "phone": { + "type": "string", + "maxLength": 9, + "minLength": 8, + "description": "Número de telefone do usuário" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Tags para agrupamento de usuários\nCom as tags o lojista pode agrupar usuários para direcionar promoções para determinados usuários, fazer uma divisão de pagamentos, definir regras de comissão, entre outras aplicações com usuários" + } + }, + "required": [ + "email" + ] + }, + "SimpleProduct": { + "title": "SimpleProduct", + "type": "object", + "description": "Modelo simplificado de um produto para atualização e criação", + "properties": { + "reference": { + "type": "string", + "description": "Código de Referência do produto" + }, + "name": { + "type": "string", + "description": "Nome do produto" + }, + "description": { + "type": "string", + "description": "Descrição do produto" + }, + "active": { + "type": "boolean", + "default": true, + "description": "Indica se o produto está ativo (`true`) ou invativo (`false`)" + }, + "tag_list": { + "type": "string", + "example": "tag1, tag2", + "description": "Tags associadas ao produto" + }, + "product_type": { + "type": "string", + "description": "Tipo de produto", + "enum": [ + "product", + "sample", + "subscription" + ], + "default": "product" + } + }, + "required": [ + "reference", + "name" + ] + }, + "Cart.simple": { + "title": "Parâmetros de carrinho resumido", + "description": "Parâmetros criação e atualização de carrinho", + "type": "object", + "properties": { + "agent": { + "type": "string", + "description": "Agente que criou o carrinho" + }, + "zip": { + "type": "string", + "description": "Código de Endereçamento Postal (CEP) do destinatário do pedido" + }, + "client_id": { + "type": "integer", + "minimum": 0, + "description": "Código identificador `ID` do cliente" + }, + "coupon_code": { + "type": "string", + "description": "Código identificador `ID` do desconto do carrinho" + }, + "email": { + "type": "string", + "deprecated": true, + "format": "email", + "description": "Email do cliente" + }, + "rebate_token": { + "type": "string", + "description": "Token do desconto" + }, + "user_id": { + "type": "number", + "description": "Id do agente" + } + } + }, + "Cart.v2": { + "title": "Lista de carrinhos", + "description": "Modelo de lista de carrinhos", + "type": "array", + "items": { + "$ref": "#/components/schemas/Cart.v1" + } + }, + "Product.v0": { + "type": "object", + "title": "Produto", + "description": "Modelo de carcaterística de produto para item no carrinho", + "properties": { + "sku": { + "type": "string", + "description": "Código SKU da variante do produto" + }, + "quantity": { + "type": "integer", + "minimum": 0, + "description": "Unidades do produto disponíveis fisicamente" + }, + "extra": { + "type": "object", + "description": "Campo para registro de observações, chave ou valores necessários" + }, + "place_id": { + "type": "integer", + "description": "Código identificador do local do produto", + "minimum": 0 + }, + "store_coupon_code": { + "type": "string", + "description": "Código de cupom" + }, + "customizations": { + "type": "array", + "description": "[Personalização](http://ajuda.vnda.com.br/pt-BR/articles/1763398-funcionalidades-produtos-personalizados) do produto", + "items": { + "properties": { + "Customization": { + "type": "string", + "description": "[Personalização](http://ajuda.vnda.com.br/pt-BR/articles/1763398-funcionalidades-produtos-personalizados) incluídas no Admin da loja. \nSe por exemplo a customização do produto é a cor, o parâmetro para a requisição deve ser `Color` ao invés de `CUstomization`. " + } + } + }, + "required": [ + "sku", + "quantity" + ] + } + } + }, + "Shipping_address": { + "title": "Endereço de envio", + "description": "Modelo de endereço de envio para carrinho e pedido", + "type": "object", + "properties": { + "first_name": { + "type": "string", + "description": "Nome do cliente" + }, + "last_name": { + "type": "string", + "description": "Sobrenome do cliente" + }, + "company_name": { + "type": "string", + "description": "Nome da empresa (para clientes jurídicos)" + }, + "email": { + "type": "string", + "format": "email", + "description": "Email do cliente" + }, + "first_phone_area": { + "type": "string", + "description": "Código de Discagem Direta à Distância (DDD)" + }, + "first_phone": { + "type": "string", + "description": "Telefone do cliente" + }, + "second_phone_area": { + "type": "string", + "description": "Código de Discagem Direta à Distância (DDD)" + }, + "second_phone": { + "type": "string", + "description": "Telefone do cliente" + }, + "recipient_name": { + "description": "Nome do recebedor" + }, + "street_name": { + "type": "string", + "description": "Logradouro" + }, + "street_number": { + "description": "Número", + "type": "string" + }, + "complement": { + "type": "string", + "description": "Complemento" + }, + "neighborhood": { + "type": "string", + "description": "Bairro" + }, + "reference": { + "type": "string", + "description": "Ponto de referência" + }, + "zip": { + "type": "string", + "description": "Código de Endereçamento Postal (CEP)" + }, + "documents": { + "type": "array", + "items": { + "type": "object", + "properties": { + "cpf": { + "description": "Cadastro de Pessoa Física", + "type": "string" + }, + "rg": { + "description": "Registro Geral", + "type": "string" + }, + "cnpj": { + "description": "Cadastro Nacional de Pessoas Jurídicas", + "type": "string" + }, + "ie": { + "type": "string", + "description": "Inscrição Estadual" + } + } + } + } + }, + "required": [ + "zip" + ] + }, + "Product.order": { + "title": "Produto em um pedido", + "description": "Modelo de produto em um pedido", + "type": "object", + "properties": { + "extra": { + "type": "object", + "description": "Dados extra do produto" + }, + "height": { + "type": "number", + "description": "Altura do produto, em centímetros." + }, + "id": { + "type": "integer", + "description": "código identificador do produto" + }, + "length": { + "type": "number", + "description": "Comprimento do produito, em centímetros." + }, + "original_price": { + "type": "number", + "description": "Preço original" + }, + "package": { + "type": "string", + "description": "Pacote do produto" + }, + "picture_url": { + "type": "string", + "description": "URL da imagem do produto" + }, + "place_city": { + "type": "string", + "nullable": true, + "description": "Cidade que o produto está" + }, + "place_id": { + "type": "integer", + "description": "Código identificador do local do produto" + }, + "place_name": { + "type": "string", + "nullable": true, + "description": "Nome do local do produto" + }, + "price": { + "type": "number", + "description": "Preço do produto" + }, + "product_id": { + "type": "integer" + }, + "product_name": { + "type": "string" + }, + "quantity": { + "type": "integer", + "description": "Unidades do produto" + }, + "reference": { + "type": "string", + "description": "Código de referência do produto" + }, + "sku": { + "type": "string", + "description": "Código SKU da variante do produto" + }, + "total": { + "type": "number", + "minimum": 0, + "description": "Valor total do produto" + }, + "variant_id": { + "type": "integer", + "description": "Código identificador da variante do produto" + }, + "variant_name": { + "type": "string", + "nullable": true, + "description": "Nome da variante do produto" + }, + "weight": { + "type": "number", + "description": "Massa do produto, em gramas" + }, + "width": { + "type": "number", + "description": "Largura do produto, em centímetros" + }, + "barcode": { + "type": "string", + "description": "Código de barras do produto" + }, + "has_customizations": { + "type": "boolean", + "description": "Indica se o produto possui customização." + } + }, + "required": [ + "extra", + "product_id", + "product_name", + "quantity", + "reference", + "sku", + "total", + "variant_id", + "variant_name", + "weight", + "width" + ] + } + }, + "securitySchemes": { + "Token": { + "type": "http", + "scheme": "bearer", + "description": "Token que deve ser enviado em todas as requisições. [Este token](http://ajuda.vnda.com.br/pt-BR/articles/1506726-chave-token-de-api) pode ser gerado pelo painel admin e não possui data de expiração. O Token e URLs gerados e utilizados no ambiente de produção serão diferentes dos que foram gerados no [ambiente de testes](http://ajuda.vnda.com.br/pt-BR/articles/3760960-ambiente-de-testes-staging). O valor de Authorization será 'Bearer seu_token'" + }, + "X-Shop-Host": { + "type": "apiKey", + "in": "header", + "name": "X-Shop-Host", + "description": "Domínio da loja como `www.nomedaloja.com.br`" + } + }, + "responses": { + "204": { + "description": "Quando um registro é atualizado", + "content": { + "application/json": { + "schema": { + "nullable": true + } + } + }, + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Id da requisição" + } + } + }, + "404": { + "description": "Quando o registro não é encontrado", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ] + }, + "examples": { + "Não encontrado": { + "value": { + "error": "not found" + } + } + } + }, + "application/xml": { + "schema": { + "type": "object", + "properties": {} + } + } + }, + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Id da requisição" + } + } + }, + "422": { + "description": "Quando os parâmetros enviados são inválidos", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/422.v1" + } + } + }, + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "Id da requisição" + } + } + }, + "Banners": { + "description": "Resposta que representa uma lista de banners", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Banner" + } + } + } + } + }, + "Banner": { + "description": "Quando um banner é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Banner" + }, + "examples": { + "example-1": { + "value": { + "big_thumb": "//b3.vnda.com.br/200x/demo%2F2019%2F02%2F25%2F19_49_32_754_home1b.jpg", + "color": "", + "description": "[COLEÇAO LA MEDINAi](https://exemplo.com/ovos-mexidos.pdf)", + "end_at": "2019-12-01T08:50:00.000-03:00", + "external": false, + "file_name": "demo%2F2019%2F02%2F25%2F19_49_32_754_home1b.jpg", + "file_uid": "demo%2F2019%2F02%2F25%2F19_49_32_754_home1b.jpg", + "html_description": "

COLEÇAO LA MEDINAi

\n", + "id": 2, + "norder": null, + "plain_description": "COLEÇAO LA MEDINAi (https://exemplo.com/ovos-mexidos.pdf)", + "small_thumb": "//b3.vnda.com.br/26x26/demo%2F2019%2F02%2F25%2F19_49_32_754_home1b.jpg", + "start_at": "2018-12-13T00:00:00.000-02:00", + "subtitle": null, + "tag": "fullbanner", + "title": "Fullbanner 2", + "updated_at": "2021-01-11T17:14:48.999-03:00", + "url": null + } + } + } + } + } + }, + "AllBanners": { + "description": "Quando existem banners dentro do período de validade", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SlimBanner" + } + } + } + } + } + }, + "Carts": { + "description": "Resposta que representa uma lista de carrinhos", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Cart" + }, + "example": [ + { + "updated_at": "2020-07-13T16:35:14.999-03:00", + "has_phone": false, + "shipping_method": null, + "items_count": 1, + "has_payment_responses": false, + "id": 23, + "email": null, + "quotation_responses_count": 0, + "payment_responses_count": 0, + "code": "799594D6EB", + "extra": {}, + "total": 12.1, + "token": "zX89SG241cST3lpzQ2nkkqJXppUJYou1jTgL" + }, + { + "shipping_method": null, + "has_phone": false, + "updated_at": "2020-07-06T22:51:17.171-03:00", + "has_payment_responses": false, + "items_count": 1, + "id": 22, + "quotation_responses_count": 0, + "payment_responses_count": 0, + "email": null, + "code": "FDF4819129", + "extra": {}, + "total": 123.1, + "token": "gt2hvYm96LNzVhcKGbR9BJnQlpaqL4kMVYAs" + } + ] + }, + "examples": { + "example-1": { + "value": [ + { + "updated_at": "2020-07-13T16:35:14.999-03:00", + "has_phone": false, + "shipping_method": null, + "items_count": 1, + "has_payment_responses": false, + "id": 23, + "email": null, + "quotation_responses_count": 0, + "payment_responses_count": 0, + "code": "799594D6EB", + "extra": {}, + "total": 12.1, + "token": "zX89SG241cST3zQ2nkkqJXUJYou1jTgL" + }, + { + "shipping_method": null, + "has_phone": false, + "updated_at": "2020-07-06T22:51:17.171-03:00", + "has_payment_responses": false, + "items_count": 1, + "id": 22, + "quotation_responses_count": 0, + "payment_responses_count": 0, + "email": null, + "code": "FDF4819129", + "extra": {}, + "total": 123.1, + "token": "gt2hvYmLNzVhcKGbR9BJnQaqL4kMVYAs" + } + ] + } + } + } + } + }, + "Channels": { + "description": "Resposta que representa uma lista de channels", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "examples": { + "Lista de channels": { + "value": [ + "direct", + "direct_app", + "ecommerce" + ] + } + } + } + }, + "headers": {} + }, + "CartItemCustomizationList": { + "description": "Resposta que representa uma lista de customizações de um item", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CartItemCustomization" + } + }, + "items": { + "$ref": "#/components/schemas/CartItemCustomization" + } + } + } + } + }, + "States": { + "description": "Resposta que representa uma lista de estados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "examples": { + "Lista de estados": { + "value": [ + "RS", + "PE", + "MG" + ] + } + } + } + } + }, + "ProductPrice": { + "description": "Resposta que representa os dados de preços de um produto e suas variantes", + "content": { + "application/json": { + "schema": { + "description": "", + "type": "object", + "x-examples": { + "example-1": { + "available": true, + "on_sale": false, + "price": 10, + "sale_price": 10, + "intl_price": 0, + "discount_rule": null, + "installments": [ + { + "number": 1, + "price": 10, + "interest": false, + "interest_rate": 0, + "total": 10 + }, + { + "number": 2, + "price": 5.15, + "interest": true, + "interest_rate": 1.5, + "total": 10.3 + } + ], + "updated_at": "", + "variants": [ + { + "main": true, + "sku": "CSMT-1", + "price": 10, + "on_sale": false, + "sale_price": 10, + "intl_price": 0, + "available": true, + "properties": {}, + "stock": 1, + "installments": [ + { + "number": 1, + "price": 10, + "interest": false, + "interest_rate": 0, + "total": 10 + }, + { + "number": 2, + "price": 5.15, + "interest": true, + "interest_rate": 1.5, + "total": 10.3 + } + ] + }, + { + "main": false, + "sku": "CSMT-2", + "price": 10, + "on_sale": false, + "sale_price": 10, + "intl_price": 0, + "available": true, + "properties": {}, + "stock": 1, + "installments": [ + { + "number": 1, + "price": 10, + "interest": false, + "interest_rate": 0, + "total": 10 + }, + { + "number": 2, + "price": 5.15, + "interest": true, + "interest_rate": 1.5, + "total": 10.3 + } + ] + } + ] + } + }, + "properties": { + "available": { + "type": "boolean" + }, + "on_sale": { + "type": "boolean" + }, + "price": { + "type": "number" + }, + "sale_price": { + "type": "number" + }, + "intl_price": { + "type": "number" + }, + "discount_rule": {}, + "installments": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "items": { + "$ref": "#/components/schemas/ProductInstallment" + } + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Data e horário da última atualização" + }, + "variants": { + "type": "array", + "uniqueItems": true, + "minItems": 1, + "items": { + "$ref": "#/components/schemas/ProductPriceVariant" + } + } + }, + "required": [ + "available", + "on_sale", + "price", + "sale_price", + "intl_price", + "installments", + "updated_at", + "variants" + ] + } + } + }, + "headers": {} + }, + "Mappings": { + "description": "Quando os mapeamentos são listados", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Mapping" + } + } + } + } + }, + "Mapping": { + "description": "Quando o mapeamento é encontrado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Mapping" + } + } + } + }, + "MappingCreate": { + "description": "Quando o mapeamento é criado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Mapping" + } + } + } + }, + "Packages": { + "description": "Resposta que representa uma lista de pacotes", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Package" + } + } + } + } + }, + "ProductImage": { + "description": "Resposta que representa uma imagem de produto", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProductImage" + } + } + } + } + }, + "ProductImages": { + "description": "Resposta que representa uma lista de imagens de produto", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProductImage" + } + } + } + } + }, + "VariantImages": { + "description": "Quando as imagens são listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "Data e horário da última atualização da imagem do produto" + } + }, + "required": [ + "url", + "updated_at" + ] + } + }, + "examples": { + "Lista de imagens": { + "value": [ + { + "url": "//b1.vnda.com.br/demo/2020/01/01/001-produto-teste-01-nome-do-produto-123.jpg?v=1234567890", + "updated_at": "2020-01-01T00:00:00.000-03:00" + }, + { + "url": "//b1.vnda.com.br/demo/2020/01/01/002-produto-teste-02-nome-do-produto-456.jpg?v=1357902468", + "updated_at": "2020-01-01T01:01:01.001-03:00" + } + ] + } + } + } + } + }, + "VariantShippings": { + "description": "Quando as formas de entrega são listadas", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "price": { + "type": "number" + }, + "description": { + "type": "string" + }, + "delivery_days": { + "type": "integer" + }, + "value_needed_to_discount": { + "type": "number", + "nullable": true + }, + "shipping_method_id": { + "type": "integer", + "nullable": true + }, + "notice": { + "type": "string", + "nullable": true + }, + "fulfillment_company": { + "type": "string", + "nullable": true + }, + "countries": { + "type": "array", + "nullable": true, + "items": { + "type": "object", + "properties": { + "country": { + "type": "string" + }, + "price": { + "type": "string" + } + } + } + } + }, + "required": [ + "name", + "value", + "price", + "description", + "delivery_days", + "value_needed_to_discount", + "shipping_method_id", + "notice", + "fulfillment_company", + "countries" + ] + } + }, + "examples": { + "Lista de formas de entrega": { + "value": [ + { + "name": "Normal", + "value": "normal", + "price": 1.25, + "description": "Prazo de até 7 dias corridos para a entrega do pedido", + "delivery_days": 7, + "value_needed_to_discount": 2.2, + "shipping_method_id": 1, + "notice": "Notice 1", + "fulfillment_company": "olist_envios", + "countries": [ + { + "country": "ARG", + "price": "1.0" + }, + { + "country": "RUS", + "price": "2.0" + } + ] + }, + { + "name": "Expressa", + "value": "expressa", + "price": 2.25, + "description": "Prazo de 1 dia corrido para a entrega do pedido", + "delivery_days": 1, + "value_needed_to_discount": null, + "shipping_method_id": null, + "fulfillment_company": null, + "notice": null, + "countries": null + } + ] + } + } + } + } + }, + "Orders": { + "description": "Lista de pedidos", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Order.v1" + } + } + } + }, + "headers": { + "X-Request-Id": { + "schema": { + "type": "string" + }, + "description": "ID da requisição" + }, + "X-Pagination": { + "schema": { + "type": "string" + }, + "description": "JSON com dados da paginação" + } + } + } + }, + "parameters": { + "only_valid": { + "name": "only_valid", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "true" + }, + "description": "Booleano indicando para filtrar banners fora do prazo de validade" + }, + "only_scheduled": { + "name": "only_scheduled", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "true" + }, + "description": "Booleano indicando para filtrar banners agendados" + }, + "only_expired": { + "name": "only_expired", + "in": "query", + "required": false, + "schema": { + "type": "string", + "example": "true" + }, + "description": "Booleano indicando para filtrar banners com prazo de validade expirados" + }, + "tag": { + "name": "tag", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Lista separada por vírgula com nomes de tags" + }, + "title": { + "name": "title", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Texto livre que permite filtrar os banners pelo título" + }, + "no_paginate": { + "name": "no_paginate", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Booleano indicando para não fazer paginação dos resultados" + }, + "page": { + "name": "page", + "in": "query", + "schema": { + "type": "integer", + "minimum": 1 + }, + "description": "Número da página atual. Os dados de paginação estarão disponíveis, em formato JSON, no header X-Pagination no response da API, caso exista paginação" + }, + "per_page": { + "name": "per_page", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "default": 100, + "maximum": 100 + }, + "description": "Número máximo de registros que deve ser retornado por página" + }, + "coupon_codes": { + "name": "coupon_codes", + "in": "query", + "required": false, + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowEmptyValue": true, + "description": "Array com os códigos de cupons" + }, + "start": { + "name": "start", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Retorna os resultados a partir desta data, no formato 'yyyy-mm-dd'" + }, + "finish": { + "name": "finish", + "in": "query", + "schema": { + "type": "string", + "format": "date" + }, + "description": "Retorna os resultados até esta data, no formato 'yyyy-mm-dd'" + }, + "status": { + "name": "status", + "in": "query", + "schema": { + "type": "string", + "example": "confirmed" + } + }, + "sort": { + "name": "sort", + "in": "query", + "schema": { + "type": "string", + "example": "updated_at,desc", + "enum": [ + "newest" + ] + }, + "description": "Ordena o resultado da busca de produtos em ordem crescente de cadastro " + }, + "order_code": { + "name": "order_code", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Código do pedido" + }, + "invoiced": { + "name": "invoiced", + "in": "query", + "schema": { + "type": "boolean" + }, + "description": "Se \"true\" retorna somente os pedidos que tenham nota fiscal. Se \"false\" retorna somente os pedidos que não tenham nota fiscal" + }, + "product_id": { + "name": "product_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "ID do produto" + }, + "cart_id": { + "name": "cart_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "ID do carrinho" + }, + "sku": { + "name": "sku", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "SKU da variante" + }, + "include_customizations_in_total": { + "name": "include_customizations_in_total", + "in": "query", + "schema": { + "type": "boolean", + "default": false + }, + "description": "Se \"true\" inclui o preço dos produtos customizados no total do pedido. Se \"false\" retorna o total do pedido sem a somatória do preço de produtos customizados.", + "required": false, + "allowEmptyValue": true + }, + "limit": { + "schema": { + "type": "integer" + }, + "in": "query", + "name": "limit", + "description": "Limite da quantidade de itens retornados" + }, + "reference": { + "schema": { + "type": "string" + }, + "in": "query", + "name": "reference", + "description": "Código de referência do produto" + }, + "ids": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + }, + "in": "query", + "name": "ids", + "description": "Filtra pelo `ID` de produtos" + }, + "updated_after": { + "schema": { + "type": "string" + }, + "in": "query", + "name": "updated_after", + "description": "Filtra produtos alterados depois da data" + }, + "include_inactive": { + "schema": { + "type": "boolean" + }, + "in": "query", + "name": "include_inactive", + "description": "Inclui na requisição os produtos inativos" + }, + "include_images": { + "schema": { + "type": "boolean" + }, + "in": "query", + "name": "include_images", + "description": "Inclui na requisição se deseja que venham todas as imagens do produto" + }, + "image_id": { + "schema": { + "type": "string" + }, + "name": "image_id", + "in": "path", + "required": true, + "description": "Código identificador `ID` da imagem" + }, + "Cart.id": { + "in": "path", + "schema": { + "type": "string" + }, + "name": "cart_id", + "required": true, + "description": "Código identificador `ID` ou `token` do carrinho" + }, + "Order.code": { + "in": "path", + "schema": { + "type": "string", + "minLength": 10, + "maxLength": 64 + }, + "name": "order_code", + "required": true, + "description": "Código identificador (`code`) ou `token` do pedido" + }, + "Package.code": { + "in": "path", + "schema": { + "type": "string", + "minLength": 1 + }, + "name": "package_code", + "required": true, + "description": "Código identificador do pacote" + } + }, + "requestBodies": { + "Product": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reference": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "active": { + "type": "boolean", + "default": true + }, + "product_type": { + "type": "string", + "enum": [ + "product", + "sample", + "subscription" + ], + "default": "product" + } + }, + "required": [ + "reference", + "name" + ] + } + } + }, + "description": "" + }, + "Variant": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sku": { + "type": "string" + }, + "name": { + "type": "string" + }, + "quantity": { + "type": "integer" + }, + "main": { + "type": "boolean" + }, + "width": { + "type": "number", + "description": "Largura do produto, em centímetros" + }, + "height": { + "type": "number", + "description": "Altura do produto, em centímetros" + }, + "length": { + "type": "number", + "description": "Comprimento do produito, em centímetros" + }, + "weight": { + "type": "number", + "description": "Massa do produto, em gramas" + }, + "handling_days": { + "type": "integer", + "description": "Dias de manuseio da variante" + }, + "price": { + "type": "number", + "description": "Preço do item" + }, + "custom_attributes": { + "type": "object", + "description": "Customização da variante" + }, + "min_quantity": { + "type": "integer" + }, + "norder": { + "type": "integer" + }, + "property1": { + "$ref": "#/components/schemas/VariantProperty" + }, + "property2": { + "$ref": "#/components/schemas/VariantProperty" + }, + "property3": { + "$ref": "#/components/schemas/VariantProperty" + }, + "barcode": { + "type": "string" + }, + "quantity_sold": { + "type": "integer", + "description": "Quantidade de itens vendidos" + } + }, + "required": [ + "sku", + "quantity", + "price" + ] + } + } + } + }, + "Orders": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "orders": { + "type": "array", + "items": { + "type": "object", + "properties": { + "code": { + "type": "string", + "description": "Código do pedido" + } + }, + "required": [ + "code" + ] + } + } + } + }, + "examples": { + "example-1": { + "value": { + "orders": [ + { + "code": "A1B2C3D4E5" + }, + { + "code": "5E4D3C2B1A" + } + ] + } + } + } + } + } + } + } + }, + "tags": [ + { + "name": "Usuários" + }, + { + "name": "Créditos" + }, + { + "name": "Pedidos" + }, + { + "name": "Pacotes" + }, + { + "name": "Produtos" + }, + { + "name": "Variantes" + }, + { + "name": "Estoque" + }, + { + "name": "Tags" + }, + { + "name": "Templates" + }, + { + "name": "Carrinhos" + }, + { + "name": "Itens do carrinho" + }, + { + "name": "Locais" + }, + { + "name": "Notas fiscais" + }, + { + "name": "Recebedores" + }, + { + "name": "Pagamentos" + }, + { + "name": "Público" + }, + { + "name": "Clientes" + }, + { + "name": "Rastreios" + }, + { + "name": "Promoções" + }, + { + "name": "Regras de desconto" + }, + { + "name": "Cupons de desconto" + }, + { + "name": "Menus" + }, + { + "name": "Mensagens do site" + }, + { + "name": "Loja" + }, + { + "name": "Personalizações" + }, + { + "name": "Mapeamentos" + }, + { + "name": "Mídias" + }, + { + "name": "Eventos" + } + ], + "security": [ + { + "Token": [] + } + ], + "x-readme": { + "explorer-enabled": true, + "proxy-enabled": true, + "samples-enabled": true + }, + "_id": "6387a48156e90b009a6ce710", + "x-stoplight": { + "id": "baml4bcuaujvs" + } +} diff --git a/vnda/utils/paths.ts b/vnda/utils/paths.ts deleted file mode 100644 index 73b6d93a7..000000000 --- a/vnda/utils/paths.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Account } from "../accounts/vnda.ts"; - -const PATHS = [ - "/carrinho", - "/carrinho/adicionar", - "/carrinho/quantidade/atualizar", - "/carrinho/produtos-sugeridos/relacionados-carrinho", - "/carrinho/remover", - "/cep", - "/cupom/ajax", -] as const; - -type Paths = (typeof PATHS)[number]; - -export const paths = ({ internalDomain: base }: Account) => { - if (!base) { - throw new Error( - "Missing `internalDomain` configuration. Open your deco admin and fill the VNDA account block", - ); - } - - return PATHS.reduce( - (acc, path) => { - acc[path] = new URL(path, base).href; - - return acc; - }, - {} as Record, - ); -}; diff --git a/vnda/utils/queryBuilder.ts b/vnda/utils/queryBuilder.ts deleted file mode 100644 index ab897a45a..000000000 --- a/vnda/utils/queryBuilder.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ProductSearchParams } from "./client/types.ts"; - -export const paramsToQueryString = ( - params: ProductSearchParams | { key: string; value: string }[], -) => { - const keys = Object.keys(params) as Array; - - const transformedValues = keys.map((_key) => { - const value = params[_key]; - const key = Array.isArray(value) ? `${_key}[]` : _key; - - if (!value) { - return []; - } - - if (Array.isArray(value)) { - return value.flatMap((v) => [key, v.toString()]); - } - - return [key, value?.toString()]; - }).filter((v) => v.length); - - return new URLSearchParams(transformedValues); -}; diff --git a/vnda/utils/segment.ts b/vnda/utils/segment.ts new file mode 100644 index 000000000..9a6b7d4b0 --- /dev/null +++ b/vnda/utils/segment.ts @@ -0,0 +1,51 @@ +import { getCookies, setCookie } from "std/http/mod.ts"; +import { AppContext } from "../mod.ts"; + +interface Segment { + agent: string; +} + +export const SEGMENT_COOKIE_NAME = "vnda_segment"; +const SEGMENT = Symbol("segment"); +const SIXTYDAYS = new Date(Date.now() + 60 * 24 * 60 * 60 * 1000); + +export const getSegmentFromBag = (ctx: AppContext): string => + ctx.bag?.get(SEGMENT); +export const setSegmentInBag = (ctx: AppContext, segment: string) => + ctx.bag?.set(SEGMENT, segment); + +export const parse = (cookie: string) => JSON.parse(atob(cookie)); + +export const buildSegmentCookie = (req: Request): string | null => { + const url = new URL(req.url); + const param = url.searchParams.get("agent"); + if (param) { + const partialSegment: string = param; + return partialSegment; + } + return null; +}; + +export const getSegmentFromCookie = ( + req: Request, +): string | undefined => { + const cookies = getCookies(req.headers); + const cookie = cookies[SEGMENT_COOKIE_NAME]; + return cookie; +}; + +export const setSegmentCookie = ( + segment: string, + headers: Headers = new Headers(), +): Headers => { + setCookie(headers, { + value: segment, + name: SEGMENT_COOKIE_NAME, + path: "/", + secure: true, + httpOnly: true, + expires: SIXTYDAYS, + }); + + return headers; +}; diff --git a/vnda/utils/transform.ts b/vnda/utils/transform.ts index 2dde95a4c..84133429f 100644 --- a/vnda/utils/transform.ts +++ b/vnda/utils/transform.ts @@ -1,43 +1,80 @@ import { Filter, + ImageObject, Offer, Product, PropertyValue, Seo, UnitPriceSpecification, } from "../../commerce/types.ts"; +import { STALE } from "../../utils/fetch.ts"; +import { AppContext } from "../mod.ts"; +import { ProductGroup, ProductPrice, SEO } from "./client/types.ts"; import { - Installment, - ProductGroup, - ProductSearchResult, + OpenAPI, + Product as OProduct, + ProductInstallment, + ProductSearch, ProductVariant, - SEO, -} from "./client/types.ts"; + VariantProductSearch, +} from "./openapi/vnda.openapi.gen.ts"; +import { getSegmentFromCookie, parse } from "./segment.ts"; + +export type VNDAProductGroup = ProductSearch | OProduct; +type VNDAProduct = VariantProductSearch | ProductVariant; interface ProductOptions { url: URL; /** Price coded currency, e.g.: USD, BRL */ priceCurrency: string; + productPrice?: ProductPrice | null; } +type TypeTags = (string | { + key: string; + value: string; + isProperty: boolean; +})[]; + export const getProductCategoryTag = ({ tags }: ProductGroup) => tags?.filter(({ type }) => type === "categoria")[0]; +export const canonicalFromTags = ( + tags: Pick[], + url: URL, +) => { + const pathname = tags.map((t) => t.name).join("/"); + return new URL(`/${pathname}`, url); +}; + export const getSEOFromTag = ( - tag: Pick, - req: Request, -): Seo => ({ - title: tag.title || "", - description: tag.description || "", - canonical: req.url, -}); + tags: Pick[], + url: URL, + seo: OpenAPI["GET /api/v2/seo_data"]["response"][0] | undefined, + hasTypeTags: boolean, + isSearchPage?: boolean, +): Seo => { + const tag = tags.at(-1); + const canonical = canonicalFromTags(tags, url); + + if (url.searchParams.has("page")) { + canonical.searchParams.set("page", url.searchParams.get("page")!); + } + + return { + title: isSearchPage ? "" : seo?.title || tag?.title || "", + description: isSearchPage ? "" : seo?.description || tag?.description || "", + canonical: canonical.href, + noIndexing: hasTypeTags, + }; +}; export const parseSlug = (slug: string) => { const segments = slug.split("-"); const id = Number(segments.at(-1)); if (!id) { - throw new Error("Malformed slug. Expecting {slug}-{id} format"); + return null; } return { @@ -46,15 +83,21 @@ export const parseSlug = (slug: string) => { }; }; -const pickVariant = (product: ProductGroup, variantId: string | null) => { - const variants = normalizeVariants(product.variants); - const [head] = variants; +export const pickVariant = ( + variants: VNDAProductGroup["variants"], + variantId: string | null, + normalize = true, +) => { + const normalizedVariants = normalize + ? normalizeVariants(variants) + : variants as VariantProductSearch[]; + const [head] = normalizedVariants; let [target, main, available]: Array< - ProductVariant | null + VNDAProduct | null > = [null, head, null]; - for (const variant of variants) { + for (const variant of normalizedVariants) { if (variant.sku === variantId) target = variant; else if (variant.main) main = variant; else if (variant.available && !available) available = variant; @@ -65,7 +108,9 @@ const pickVariant = (product: ProductGroup, variantId: string | null) => { return target || fallback || head; }; -const normalizeInstallments = (installments: Installment[] | number[] = []) => { +const normalizeInstallments = ( + installments: ProductInstallment[] | number[] = [], +) => { if (typeof installments[0] === "number") { const total = (installments as number[]).reduce((acc, curr) => acc + curr); @@ -76,7 +121,9 @@ const normalizeInstallments = (installments: Installment[] | number[] = []) => { }]; } - return (installments as Installment[]).map(({ number, price, total }) => ({ + return (installments as ProductInstallment[]).map(( + { number, price, total }, + ) => ({ number, price, total, @@ -85,15 +132,16 @@ const normalizeInstallments = (installments: Installment[] | number[] = []) => { const toURL = (src: string) => src.startsWith("//") ? `https:${src}` : src; -const toOffer = ({ +export const toOffer = ({ price, sale_price, + intl_price, available_quantity, available, installments = [], -}: ProductVariant): Offer | null => { +}: VNDAProduct & { intl_price?: number }): Offer[] => { if (!price || !sale_price) { - return null; + return []; } const priceSpecification: UnitPriceSpecification[] = [{ @@ -123,8 +171,8 @@ const toOffer = ({ }); } - return { - "@type": "Offer", + const offers: Offer[] = [{ + "@type": "Offer" as const, seller: "VNDA", price, priceSpecification, @@ -134,10 +182,33 @@ const toOffer = ({ availability: available ? "https://schema.org/InStock" : "https://schema.org/OutOfStock", - }; + }]; + + if (intl_price) { + offers.push({ + "@type": "Offer", + seller: "VNDA_INTL", + price: intl_price, + priceSpecification: [{ + "@type": "UnitPriceSpecification", + priceType: "https://schema.org/SalePrice", + price: intl_price, + }], + inventoryLevel: { + value: available_quantity, + }, + availability: available + ? "https://schema.org/InStock" + : "https://schema.org/OutOfStock", + // Static since VNDA only have a BRL price and USD when intl_price is available + priceCurrency: "USD", + }); + } + + return offers; }; -const toPropertyValue = (variant: ProductVariant): PropertyValue[] => +const toPropertyValue = (variant: VNDAProduct): PropertyValue[] => Object.values(variant.properties ?? {}) .filter(Boolean) .map(({ value, name }) => @@ -149,36 +220,89 @@ const toPropertyValue = (variant: ProductVariant): PropertyValue[] => } as PropertyValue) ).filter((x): x is PropertyValue => Boolean(x)); +const toPropertyValueTags = (tags: ProductSearch["tags"]): PropertyValue[] => + tags?.map((tag) => + tag && ({ + "@type": "PropertyValue", + name: tag.name, + value: JSON.stringify(tag), + valueReference: "TAGS", + } as PropertyValue) + ); + +const toPropertyValueCategoryTags = ( + categoryTags: OProduct["category_tags"], +) => { + if (!categoryTags) return []; + + return categoryTags.map((tag) => { + return { + "@type": "PropertyValue", + name: tag.tag_type, + value: tag.name, + description: tag.title, + valueReference: "TAGS", + } as PropertyValue; + }); +}; + // deno-lint-ignore no-explicit-any -const isProductVariant = (p: any): p is ProductVariant => +const isProductVariant = (p: any): p is VariantProductSearch => typeof p.id === "number"; const normalizeVariants = ( - variants: ProductGroup["variants"] = [], -): ProductVariant[] => - variants.flatMap((v) => isProductVariant(v) ? [v] : Object.values(v)); + variants: VNDAProductGroup["variants"] = [], +): VNDAProduct[] => + variants.flatMap((v) => + isProductVariant(v) ? [v] : Object.values(v) as VNDAProduct[] + ); + +const toImageObjectVideo = ( + video: OpenAPI["GET /api/v2/products/:productId/videos"]["response"], +): ImageObject[] => + video?.map(({ url, embed_url, thumbnail_url }) => ({ + "@type": "ImageObject", + encodingFormat: "video", + contentUrl: url, + thumbnailUrl: thumbnail_url, + embedUrl: embed_url, + } as ImageObject)); export const toProduct = ( - product: ProductGroup, + product: VNDAProductGroup, variantId: string | null, options: ProductOptions, level = 0, ): Product => { - const { url, priceCurrency } = options; - const variant = pickVariant(product, variantId); + const { url, priceCurrency, productPrice } = options; + const variant = pickVariant(product.variants, variantId); const variants = normalizeVariants(product.variants); + const variantPrices = productPrice?.variants + ? pickVariant( + productPrice.variants as VNDAProductGroup["variants"], + variantId, + false, + ) + : null; + const offers = toOffer(variantPrices ?? variant); + const variantUrl = new URL( `/produto/${product.slug}-${product.id}?skuId=${variant.sku}`, url.origin, ).href; + const productUrl = new URL( `/produto/${product.slug}-${product.id}`, url.origin, ).href; + const productID = `${variant.sku}`; const productGroupID = `${product.id}`; - const offer = toOffer(variant); - const offers = offer ? [offer] : []; + + const myTags = "tags" in product ? product.tags : []; + const myCategoryTags = "category_tags" in product + ? product.category_tags + : []; return { "@type": "Product", @@ -187,7 +311,11 @@ export const toProduct = ( url: variantUrl, name: product.name, description: product.description, - additionalProperty: toPropertyValue(variant), + additionalProperty: [ + ...toPropertyValue(variant), + ...toPropertyValueTags(myTags), + ...toPropertyValueCategoryTags(myCategoryTags), + ], inProductGroupWithID: productGroupID, gtin: product.reference, isVariantOf: { @@ -201,16 +329,27 @@ export const toProduct = ( ? variants.map((v) => toProduct(product, v.sku!, options, 1)) : [], }, - image: [{ - "@type": "ImageObject", - alternateName: product.name ?? "", - url: toURL(product.image_url ?? ""), - }], + image: product.images?.length ?? 0 > 1 + ? product.images?.map((img) => ({ + "@type": "ImageObject" as const, + encodingFormat: "image", + alternateName: `${img.url}`, + url: toURL(img.url!), + })) + : [ + { + "@type": "ImageObject", + encodingFormat: "image", + alternateName: product.name ?? "", + url: toURL(product.image_url ?? ""), + }, + ], + // images: offers: { "@type": "AggregateOffer", priceCurrency: priceCurrency, - highPrice: product.price!, - lowPrice: product.sale_price!, + highPrice: productPrice?.price ?? product.price!, + lowPrice: productPrice?.sale_price ?? product.sale_price!, offerCount: offers.length, offers: offers, }, @@ -236,27 +375,60 @@ const removeFilter = ( filter: { key: string; value: string }, ) => typeTagsInUse.filter((inUse) => - inUse.key !== filter.key && - inUse.value !== filter.value + !(inUse.key === filter.key && inUse.value === filter.value) ); export const toFilters = ( - aggregations: ProductSearchResult["aggregations"], + aggregations: + OpenAPI["GET /api/v2/products/search"]["response"]["aggregations"], typeTagsInUse: { key: string; value: string }[], cleanUrl: URL, ): Filter[] => { + if (!aggregations) { + return []; + } + const priceRange = { "@type": "FilterRange" as const, label: "Valor", key: "price_range", values: { - min: aggregations.min_price, - max: aggregations.max_price, + min: aggregations.min_price!, + max: aggregations.max_price!, }, }; - const types = Object.keys(aggregations.types).map((typeKey) => { - const typeValues = aggregations.types[typeKey]; + const combinedFiltersKeys = Object.keys(aggregations.types ?? {}).concat( + ...Object.keys(aggregations.properties ?? {}), + ); + + const types = combinedFiltersKeys.map((typeKey) => { + const isProperty = typeKey.includes("property"); + + interface AggregationType { + name: string; + title: string; + count: number; + value: string; + } + const typeValues: AggregationType[] = isProperty + // deno-lint-ignore no-explicit-any + ? ((aggregations.properties as any)[ + typeKey as string + ] as AggregationType[]) + // deno-lint-ignore no-explicit-any + : ((aggregations.types as any)[ + typeKey as string + ] as AggregationType[]); + + if (isProperty) { + typeValues.forEach((obj) => { + if (obj.value) { + obj.title = obj.value; + obj.name = obj.value; + } + }); + } return { "@type": "FilterToggle" as const, @@ -293,25 +465,93 @@ export const toFilters = ( ]; }; -export const typeTagExtractor = (url: URL) => { +export const typeTagExtractor = (url: URL, tags: { type?: string }[]) => { + const cleanUrl = new URL(url); const keysToDestroy: string[] = []; - const typeTags: { key: string; value: string }[] = []; - const typeTagRegex = /\btype_tags\[(\S+)\]\[\]/; + const typeTags: { key: string; value: string; isProperty: boolean }[] = []; + const typeTagRegex = /\btype_tags\[(.*?)\]\[\]/; - url.searchParams.forEach((value, key) => { + cleanUrl.searchParams.forEach((value, key) => { const match = typeTagRegex.exec(key); if (match) { - keysToDestroy.push(key); - typeTags.push({ key, value }); + const tagValue = match[1]; + const isProperty = tagValue.includes("property"); + if (tags.some((tag) => tag.type === tagValue) || isProperty) { + keysToDestroy.push(key); + typeTags.push({ key, value, isProperty }); + } } }); - // it can't be done inside the forEach instruction above - typeTags.forEach((tag) => url.searchParams.delete(tag.key)); + keysToDestroy.forEach((key) => cleanUrl.searchParams.delete(key)); + + cleanUrl.searchParams.delete("page"); return { typeTags, - cleanUrl: url, + cleanUrl, }; }; + +export const addVideoToProduct = ( + product: Product, + video: OpenAPI["GET /api/v2/products/:productId/videos"]["response"] | null, +): Product => ({ + ...product, + image: [ + ...(product?.image ?? []), + ...(video ? toImageObjectVideo(video) : []), + ], +}); + +export const fetchAndApplyPrices = async ( + products: Product[], + priceCurrency: string, + req: Request, + ctx: AppContext, +): Promise => { + const segmentCookie = getSegmentFromCookie(req); + const segment = segmentCookie ? parse(segmentCookie) : null; + + const pricePromises = products.map((product) => + ctx.api["GET /api/v2/products/:productId/price"]({ + productId: product.sku, + coupon_codes: segment?.cc ? [segment.cc] : [], + }, STALE) + ); + + const priceResults = await Promise.all(pricePromises); + + return products.map((product) => { + const matchingPriceInfo = priceResults.find((priceResult) => + (priceResult as unknown as ProductPrice).variants.some((variant) => + variant.sku === product.sku + ) + ) as unknown as ProductPrice; + + const variantPrices = matchingPriceInfo?.variants + ? pickVariant( + matchingPriceInfo.variants as VNDAProductGroup["variants"], + product.sku, + false, + ) + : null; + + if (!variantPrices) return product; + + const offers = toOffer(variantPrices); + + return { + ...product, + offers: { + "@type": "AggregateOffer" as const, + priceCurrency: priceCurrency, + highPrice: variantPrices?.price ?? product.offers?.highPrice ?? 0, + lowPrice: variantPrices?.sale_price ?? product.offers?.lowPrice ?? 0, + offerCount: offers.length, + offers: offers, + }, + }; + }); +}; diff --git a/vtex/README.md b/vtex/README.md new file mode 100644 index 000000000..c3faab1e3 --- /dev/null +++ b/vtex/README.md @@ -0,0 +1,6 @@ +VTEX is a cloud-based e-commerce platform and digital commerce company that provides a comprehensive set of tools and services for businesses looking to establish and manage their online retail operations. + +This app wrapps VTEX API into a comprehensive set of loaders/actions/workflows +empowering non technical users to interact and act upon their headless commerce. + +If you want to use a custom search engine (Algolia, Typesense etc), you will need to fill the App Key & App Token properties. For these, follow this guide diff --git a/vtex/actions/address/createAddress.ts b/vtex/actions/address/createAddress.ts new file mode 100644 index 000000000..af849c53a --- /dev/null +++ b/vtex/actions/address/createAddress.ts @@ -0,0 +1,67 @@ +import { AppContext } from "../../mod.ts"; +import { parseCookie } from "../../utils/vtexId.ts"; + +interface AddressInput { + name?: string; + addressName: string; + addressType?: string; + city?: string; + complement?: string; + country?: string; + geoCoordinates?: number[]; + neighborhood?: string; + number?: string; + postalCode?: string; + receiverName?: string; + reference?: string; + state?: string; + street?: string; +} + +interface SavedAddress { + id: string; + cacheId: string; +} + +async function action( + props: AddressInput, + req: Request, + ctx: AppContext, +): Promise< + | SavedAddress + | null +> { + const { io } = ctx; + const { cookie } = parseCookie(req.headers, ctx.account); + + const mutation = ` + mutation SaveAddress($address: AddressInput!) { + saveAddress(address: $address) @context(provider: "vtex.store-graphql") { + id + cacheId + } + }`; + + try { + const { saveAddress: savedAddress } = await io.query< + { saveAddress: SavedAddress }, + { address: AddressInput } + >( + { + query: mutation, + operationName: "SaveAddress", + variables: { + address: props, + }, + }, + { headers: { cookie } }, + ); + + return savedAddress; + } catch (error) { + console.error("Error saving address:", error); + return null; + } +} + +export default action; diff --git a/vtex/actions/address/deleteAddress.ts b/vtex/actions/address/deleteAddress.ts new file mode 100644 index 000000000..760afd133 --- /dev/null +++ b/vtex/actions/address/deleteAddress.ts @@ -0,0 +1,58 @@ +import { AppContext } from "../../mod.ts"; +import { parseCookie } from "../../utils/vtexId.ts"; + +interface DeleteAddress { + addressId: string; +} + +interface AddressInput { + addressId: string; +} + +async function action( + { addressId }: AddressInput, + req: Request, + ctx: AppContext, +) { + const { io } = ctx; + const { cookie } = parseCookie(req.headers, ctx.account); + + const mutation = ` + mutation DeleteAddress($addressId: String) { + deleteAddress(id: $addressId) { + cacheId + addresses: address { + addressId: id + addressType + addressName + city + complement + country + neighborhood + number + postalCode + geoCoordinates + receiverName + reference + state + street + } + } + }`; + + try { + return await io.query( + { + query: mutation, + operationName: "DeleteAddress", + variables: { addressId }, + }, + { headers: { cookie } }, + ); + } catch (error) { + console.error("Error deleting address:", error); + return null; + } +} + +export default action; diff --git a/vtex/actions/address/updateAddress.ts b/vtex/actions/address/updateAddress.ts new file mode 100644 index 000000000..136ed41bc --- /dev/null +++ b/vtex/actions/address/updateAddress.ts @@ -0,0 +1,92 @@ +import { PostalAddressVTEX } from "../../../commerce/types.ts"; +import { AppContext } from "../../mod.ts"; +import { parseCookie } from "../../utils/vtexId.ts"; + +interface Address { + name?: string; + addressName?: string; + addressType?: string; + city?: string; + complement: string | null; + country?: string; + geoCoordinates?: number[]; + neighborhood?: string; + number?: string; + postalCode?: string; + receiverName: string | null; + reference?: string; + state?: string; + street?: string; + addressId: string; +} + +async function action( + props: Address, + req: Request, + ctx: AppContext, +): Promise< + | PostalAddressVTEX + | null +> { + const { io } = ctx; + const { cookie } = parseCookie(req.headers, ctx.account); + const { addressId, ...addressFields } = props; + + const mutation = ` + mutation UpdateAddress($addressId: String!, $addressFields: AddressInput) { + updateAddress(id: $addressId, fields: $addressFields) + @context(provider: "vtex.store-graphql") { + cacheId + addresses: address { + addressId: id + addressType + addressName + city + complement + country + neighborhood + number + postalCode + geoCoordinates + receiverName + reference + state + street + } + } + } + `; + + try { + const { updateAddress: updatedAddress } = await io.query< + { updateAddress: Address }, + { addressId: string; addressFields: Omit } + >( + { + query: mutation, + operationName: "UpdateAddress", + variables: { + addressId, + addressFields, + }, + }, + { headers: { cookie } }, + ); + + return { + "@type": "PostalAddress", + addressCountry: updatedAddress?.country, + addressLocality: updatedAddress?.city, + addressRegion: updatedAddress?.state, + postalCode: updatedAddress?.postalCode, + streetAddress: updatedAddress?.street, + receiverName: updatedAddress?.receiverName, + complement: updatedAddress?.complement, + addressId: updatedAddress?.addressId, + }; + } catch (error) { + console.error("Error updating address:", error); + return null; + } +} +export default action; diff --git a/vtex/actions/analytics/sendEvent.ts b/vtex/actions/analytics/sendEvent.ts new file mode 100644 index 000000000..061cb3488 --- /dev/null +++ b/vtex/actions/analytics/sendEvent.ts @@ -0,0 +1,76 @@ +// Intelligent Search analytics integration +import { AppContext } from "../../mod.ts"; +import { getISCookiesFromBag } from "../../utils/intelligentSearch.ts"; + +export type Props = + | { + type: "session.ping"; + url: string; + } + | { + type: "page.cart"; + products: { + productId: string; + quantity: number; + }[]; + } + | { + type: "page.empty_cart"; + products: []; + } + | { + type: "page.confirmation"; + order: string; + products: { + productId: string; + quantity: number; + price: number; + }[]; + } + | { + type: "search.click"; + position: number; + text: string; + productId: string; + url: string; + } + | { + type: "search.query"; + url: string; + text: string; + misspelled: boolean; + match: number; + operator: string; + locale: string; + }; + +/** + * @docs https://developers.vtex.com/docs/api-reference/checkout-api#post-/api/checkout/pub/orderForm/-orderFormId-/items + */ +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { sp } = ctx; + const cookies = getISCookiesFromBag(ctx); + + if (!cookies) { + throw new Error("Missing IS Cookies"); + } + + await sp["POST /event-api/v1/:account/event"]({ account: ctx.account }, { + body: { + ...props, + ...cookies, + agent: req.headers.get("user-agent") || "deco-sites/apps", + }, + headers: { + "content-type": "application/json", + }, + }); + + return null; +}; + +export default action; diff --git a/vtex/actions/cart/addItems.ts b/vtex/actions/cart/addItems.ts new file mode 100644 index 000000000..e538cd05c --- /dev/null +++ b/vtex/actions/cart/addItems.ts @@ -0,0 +1,63 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; +import type { OrderForm } from "../../utils/types.ts"; +import { forceHttpsOnAssets } from "../../utils/transform.ts"; + +export interface Item { + quantity: number; + seller: string; + id: string; + index?: number; + price?: number; +} + +export interface Props { + orderItems: Item[]; + allowedOutdatedData?: Array<"paymentData">; +} + +/** + * @docs https://developers.vtex.com/docs/api-reference/checkout-api#post-/api/checkout/pub/orderForm/-orderFormId-/items + */ +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { + orderItems, + allowedOutdatedData = ["paymentData"], + } = props; + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + const segment = getSegmentFromBag(ctx); + + try { + const response = await vcsDeprecated + ["POST /api/checkout/pub/orderForm/:orderFormId/items"]({ + orderFormId, + allowedOutdatedData, + sc: segment?.payload.channel, + }, { + body: { orderItems }, + headers: { + "content-type": "application/json", + accept: "application/json", + cookie, + }, + }); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return forceHttpsOnAssets((await response.json()) as OrderForm); + } catch (error) { + console.error(error); + + throw error; + } +}; + +export default action; diff --git a/vtex/actions/cart/addOfferings.ts b/vtex/actions/cart/addOfferings.ts new file mode 100644 index 000000000..07fe50b88 --- /dev/null +++ b/vtex/actions/cart/addOfferings.ts @@ -0,0 +1,52 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import { forceHttpsOnAssets } from "../../utils/transform.ts"; +import { OrderForm } from "../../utils/types.ts"; +import { DEFAULT_EXPECTED_SECTIONS } from "./updateItemAttachment.ts"; + +export interface Props { + index: number; + id: number; + expectedOrderFormSections?: string[]; +} + +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +) => { + const { index, id, expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS } = + props; + const { vcsDeprecated } = ctx; + + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + + try { + const response = await vcsDeprecated + ["POST /api/checkout/pub/orderForm/:orderFormId/items/:index/offerings"]({ + orderFormId, + index, + }, { + body: { + expectedOrderFormSections, + id, + info: null, + }, + headers: { + "content-type": "application/json", + accept: "application/json", + cookie, + }, + }); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return forceHttpsOnAssets((await response.json()) as OrderForm); + } catch (error) { + throw error; + } +}; + +export default action; diff --git a/vtex/actions/cart/clearOrderformMessages.ts b/vtex/actions/cart/clearOrderformMessages.ts new file mode 100644 index 000000000..c93c4c307 --- /dev/null +++ b/vtex/actions/cart/clearOrderformMessages.ts @@ -0,0 +1,30 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import type { OrderForm } from "../../utils/types.ts"; + +const action = async ( + _props: unknown, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + + const response = await vcsDeprecated[ + "POST /api/checkout/pub/orderForm/:orderFormId/messages/clear" + ]( + { orderFormId }, + { + headers: { accept: "application/json", cookie }, + body: {}, + }, + ); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/cart/getInstallment.ts b/vtex/actions/cart/getInstallment.ts new file mode 100644 index 000000000..bd22655d8 --- /dev/null +++ b/vtex/actions/cart/getInstallment.ts @@ -0,0 +1,36 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import type { InstallmentOption } from "../../utils/types.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; + +export interface Props { + paymentSystem: number; +} + +/** + * @docs https://developers.vtex.com/docs/api-reference/checkout-api#get-/api/checkout/pub/orderForm/-orderFormId-/installments + */ +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { paymentSystem } = props; + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + const segment = getSegmentFromBag(ctx); + + const response = await vcsDeprecated + ["GET /api/checkout/pub/orderForm/:orderFormId/installments"]( + { orderFormId, paymentSystem, sc: segment?.payload.channel }, + { headers: { accept: "application/json", cookie } }, + ); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/cart/removeItemAttachment.ts b/vtex/actions/cart/removeItemAttachment.ts new file mode 100644 index 000000000..f0e5f14bd --- /dev/null +++ b/vtex/actions/cart/removeItemAttachment.ts @@ -0,0 +1,70 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import type { OrderForm } from "../../utils/types.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; + +export interface Props { + /** @description index of the item in the cart.items array you want to edit */ + index: number; + /** @description attachment name */ + attachment: string; + content: Record; + expectedOrderFormSections?: string[]; + noSplitItem?: boolean; +} + +export const DEFAULT_EXPECTED_SECTIONS = [ + "items", + "totalizers", + "clientProfileData", + "shippingData", + "paymentData", + "sellers", + "messages", + "marketingData", + "clientPreferencesData", + "storePreferencesData", + "giftRegistryData", + "ratesAndBenefitsData", + "openTextField", + "commercialConditionData", + "customData", +]; + +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { + index, + attachment, + content, + noSplitItem = true, + expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS, + } = props; + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + const segment = getSegmentFromBag(ctx); + + const response = await vcsDeprecated + ["DELETE /api/checkout/pub/orderForm/:orderFormId/items/:index/attachments/:attachment"]( + { orderFormId, attachment, index, sc: segment?.payload.channel }, + { + body: { content, noSplitItem, expectedOrderFormSections }, + headers: { + accept: "application/json", + "content-type": "application/json", + cookie, + }, + }, + ); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/cart/removeItems.ts b/vtex/actions/cart/removeItems.ts new file mode 100644 index 000000000..d2e9c2d82 --- /dev/null +++ b/vtex/actions/cart/removeItems.ts @@ -0,0 +1,37 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import type { OrderForm } from "../../utils/types.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; + +/** + * @docs https://developers.vtex.com/docs/api-reference/checkout-api#post-/api/checkout/pub/orderForm/-orderFormId-/items/removeAll + */ +const action = async ( + _props: unknown, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + const segment = getSegmentFromBag(ctx); + + const response = await vcsDeprecated + ["POST /api/checkout/pub/orderForm/:orderFormId/items/removeAll"]( + { orderFormId, sc: segment?.payload.channel }, + { + headers: { + "content-type": "application/json", + accept: "application/json", + cookie, + }, + }, + ); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/cart/removeOffering.ts b/vtex/actions/cart/removeOffering.ts new file mode 100644 index 000000000..393cb84d8 --- /dev/null +++ b/vtex/actions/cart/removeOffering.ts @@ -0,0 +1,54 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import { forceHttpsOnAssets } from "../../utils/transform.ts"; +import { OrderForm } from "../../utils/types.ts"; +import { DEFAULT_EXPECTED_SECTIONS } from "./updateItemAttachment.ts"; + +export interface Props { + index: number; + id: number; + expectedOrderFormSections?: string[]; +} + +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +) => { + const { index, id, expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS } = + props; + const { vcsDeprecated } = ctx; + + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + + try { + const response = await vcsDeprecated + ["POST /api/checkout/pub/orderForm/:orderFormId/items/:index/offerings/:id/remove"]( + { + orderFormId, + id, + index: index, + }, + { + body: { + expectedOrderFormSections, + }, + headers: { + "content-type": "application/json", + accept: "application/json", + cookie, + }, + }, + ); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return forceHttpsOnAssets((await response.json()) as OrderForm); + } catch (error) { + throw error; + } +}; + +export default action; diff --git a/vtex/actions/cart/simulation.ts b/vtex/actions/cart/simulation.ts new file mode 100644 index 000000000..046d91280 --- /dev/null +++ b/vtex/actions/cart/simulation.ts @@ -0,0 +1,51 @@ +import { AppContext } from "../../mod.ts"; +import type { SimulationOrderForm } from "../../utils/types.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; + +export interface Item { + id: number; + quantity: number; + seller: string; +} + +export interface Props { + items: Item[]; + postalCode: string; + country: string; + RnbBehavior?: 0 | 1; +} + +/** + * @docs https://developers.vtex.com/docs/api-reference/checkout-api#post-/api/checkout/pub/orderForms/simulation + */ +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const cookie = req.headers.get("cookie") ?? ""; + const { vcsDeprecated } = ctx; + const { items, postalCode, country, RnbBehavior = 1 } = props; + const segment = getSegmentFromBag(ctx); + + const response = await vcsDeprecated[ + "POST /api/checkout/pub/orderForms/simulation" + ]( + { + RnbBehavior, + sc: segment?.payload.channel, + }, + { + body: { items, country, postalCode }, + headers: { + accept: "application/json", + "content-type": "application/json", + cookie, + }, + }, + ); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/cart/updateAttachment.ts b/vtex/actions/cart/updateAttachment.ts new file mode 100644 index 000000000..4b5e7c014 --- /dev/null +++ b/vtex/actions/cart/updateAttachment.ts @@ -0,0 +1,49 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import type { OrderForm } from "../../utils/types.ts"; +import { DEFAULT_EXPECTED_SECTIONS } from "./updateItemAttachment.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; + +export interface Props { + attachment: string; + expectedOrderFormSections?: string[]; + // deno-lint-ignore no-explicit-any + body: any; +} + +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { + attachment, + body, + expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS, + } = props; + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + const segment = getSegmentFromBag(ctx); + + const response = await vcsDeprecated + ["POST /api/checkout/pub/orderForm/:orderFormId/attachments/:attachment"]({ + orderFormId, + attachment, + sc: segment?.payload.channel, + }, { + body: { expectedOrderFormSections, ...body }, + headers: { + accept: "application/json", + "content-type": "application/json", + cookie, + }, + }); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/cart/updateCoupons.ts b/vtex/actions/cart/updateCoupons.ts new file mode 100644 index 000000000..79ebce2a5 --- /dev/null +++ b/vtex/actions/cart/updateCoupons.ts @@ -0,0 +1,43 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import type { OrderForm } from "../../utils/types.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; + +export interface Props { + text: string; +} + +/** + * @docs https://developers.vtex.com/docs/api-reference/checkout-api#post-/api/checkout/pub/orderForm/-orderFormId-/coupons + */ +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { text } = props; + const cookie = req.headers.get("cookie") ?? ""; + const { orderFormId } = parseCookie(req.headers); + const segment = getSegmentFromBag(ctx); + + const response = await vcsDeprecated + ["POST /api/checkout/pub/orderForm/:orderFormId/coupons"]({ + orderFormId, + sc: segment?.payload.channel, + }, { + body: { text }, + headers: { + accept: "application/json", + "content-type": "application/json", + cookie, + }, + }); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/cart/updateGifts.ts b/vtex/actions/cart/updateGifts.ts new file mode 100644 index 000000000..a5b4c8e99 --- /dev/null +++ b/vtex/actions/cart/updateGifts.ts @@ -0,0 +1,40 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import type { OrderForm, SelectableGifts } from "../../utils/types.ts"; +import { DEFAULT_EXPECTED_SECTIONS } from "./updateItemAttachment.ts"; + +export interface Props extends SelectableGifts { + expectedOrderFormSections?: string[]; +} + +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { + expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS, + id, + selectedGifts, + } = props; + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + + const response = await vcsDeprecated[ + "POST /api/checkout/pub/orderForm/:orderFormId/selectable-gifts/:giftId" + ]( + { orderFormId, giftId: id }, + { + headers: { accept: "application/json", cookie }, + body: { expectedOrderFormSections, selectedGifts, id }, + }, + ); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/cart/updateItemAttachment.ts b/vtex/actions/cart/updateItemAttachment.ts new file mode 100644 index 000000000..534c5487b --- /dev/null +++ b/vtex/actions/cart/updateItemAttachment.ts @@ -0,0 +1,75 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import type { OrderForm } from "../../utils/types.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; + +export interface Props { + /** @description index of the item in the cart.items array you want to edit */ + index: number; + /** @description attachment name */ + attachment: string; + content: Record; + expectedOrderFormSections?: string[]; + noSplitItem?: boolean; +} + +export const DEFAULT_EXPECTED_SECTIONS = [ + "items", + "totalizers", + "clientProfileData", + "shippingData", + "paymentData", + "sellers", + "messages", + "marketingData", + "clientPreferencesData", + "storePreferencesData", + "giftRegistryData", + "ratesAndBenefitsData", + "openTextField", + "commercialConditionData", + "customData", +]; + +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { + index, + attachment, + content, + noSplitItem = true, + expectedOrderFormSections = DEFAULT_EXPECTED_SECTIONS, + } = props; + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + const segment = getSegmentFromBag(ctx); + + const response = await vcsDeprecated + ["POST /api/checkout/pub/orderForm/:orderFormId/items/:index/attachments/:attachment"]( + { + orderFormId, + attachment, + index, + sc: segment?.payload.channel, + }, + { + body: { content, noSplitItem, expectedOrderFormSections }, + headers: { + accept: "application/json", + "content-type": "application/json", + cookie, + }, + }, + ); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/cart/updateItemPrice.ts b/vtex/actions/cart/updateItemPrice.ts new file mode 100644 index 000000000..32108d754 --- /dev/null +++ b/vtex/actions/cart/updateItemPrice.ts @@ -0,0 +1,48 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import type { OrderForm } from "../../utils/types.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; + +export interface Props { + itemIndex: number; + price: number; +} + +/** + * @docs https://developers.vtex.com/docs/api-reference/checkout-api#put-/api/checkout/pub/orderForm/-orderFormId-/items/-itemIndex-/price + */ +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { + itemIndex, + price, + } = props; + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + const segment = getSegmentFromBag(ctx); + + const response = await vcsDeprecated + ["PUT /api/checkout/pub/orderForm/:orderFormId/items/:index/price"]({ + orderFormId, + index: itemIndex, + sc: segment?.payload.channel, + }, { + body: { price }, + headers: { + accept: "application/json", + "content-type": "application/json", + cookie, + }, + }); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/cart/updateItems.ts b/vtex/actions/cart/updateItems.ts new file mode 100644 index 000000000..6bcc59613 --- /dev/null +++ b/vtex/actions/cart/updateItems.ts @@ -0,0 +1,53 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; +import type { OrderForm } from "../../utils/types.ts"; + +export interface Item { + quantity: number; + index: number; +} + +export interface Props { + orderItems: Item[]; + allowedOutdatedData?: Array<"paymentData">; +} + +/** + * @docs https://developers.vtex.com/docs/api-reference/checkout-api#post-/api/checkout/pub/orderForm/-orderFormId-/items/update + */ +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { + orderItems, + allowedOutdatedData = ["paymentData"], + } = props; + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + const segment = getSegmentFromBag(ctx); + + const response = await vcsDeprecated + ["POST /api/checkout/pub/orderForm/:orderFormId/items/update"]({ + orderFormId, + allowedOutdatedData, + sc: segment?.payload.channel, + }, { + body: { orderItems }, + headers: { + "content-type": "application/json", + accept: "application/json", + cookie, + }, + }); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/cart/updateProfile.ts b/vtex/actions/cart/updateProfile.ts new file mode 100644 index 000000000..ae54aeefe --- /dev/null +++ b/vtex/actions/cart/updateProfile.ts @@ -0,0 +1,43 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import type { OrderForm } from "../../utils/types.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; + +export interface Props { + ignoreProfileData: boolean; +} + +/** + * @docs https://developers.vtex.com/docs/api-reference/checkout-api#patch-/api/checkout/pub/orderForm/-orderFormId-/profile + */ +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { ignoreProfileData } = props; + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + const segment = getSegmentFromBag(ctx); + + const response = await vcsDeprecated + ["PATCH /api/checkout/pub/orderForm/:orderFormId/profile"]({ + orderFormId, + sc: segment?.payload.channel, + }, { + body: { ignoreProfileData }, + headers: { + "content-type": "application/json", + accept: "application/json", + cookie, + }, + }); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/cart/updateUser.ts b/vtex/actions/cart/updateUser.ts new file mode 100644 index 000000000..267088306 --- /dev/null +++ b/vtex/actions/cart/updateUser.ts @@ -0,0 +1,36 @@ +import { AppContext } from "../../mod.ts"; +import { proxySetCookie } from "../../utils/cookies.ts"; +import { parseCookie } from "../../utils/orderForm.ts"; +import type { OrderForm } from "../../utils/types.ts"; +import { getSegmentFromBag } from "../../utils/segment.ts"; + +/** + * @docs https://developers.vtex.com/docs/api-reference/checkout-api#get-/checkout/changeToAnonymousUser/-orderFormId- + */ +const action = async ( + _props: unknown, + req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const { orderFormId } = parseCookie(req.headers); + const cookie = req.headers.get("cookie") ?? ""; + const segment = getSegmentFromBag(ctx); + + const response = await vcsDeprecated + ["GET /api/checkout/changeToAnonymousUser/:orderFormId"]({ + orderFormId, + sc: segment?.payload.channel, + }, { + headers: { + accept: "application/json", + cookie, + }, + }); + + proxySetCookie(response.headers, ctx.response.headers, req.url); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/masterdata/createDocument.ts b/vtex/actions/masterdata/createDocument.ts new file mode 100644 index 000000000..46619a3fd --- /dev/null +++ b/vtex/actions/masterdata/createDocument.ts @@ -0,0 +1,47 @@ +import { AppContext } from "../../mod.ts"; +import { parseCookie } from "../../utils/vtexId.ts"; +import type { CreateNewDocument } from "../../utils/types.ts"; + +export interface Props { + data: Record; + acronym: string; + isPrivateEntity?: boolean; +} + +/** + * @docs https://developers.vtex.com/docs/api-reference/masterdata-api#post-/api/dataentities/-acronym-/documents + */ +const action = async ( + props: Props, + req: Request, + ctx: AppContext, + /* no-explicit-any */ +): Promise => { + const { vcs, vcsDeprecated } = ctx; + const { data, acronym, isPrivateEntity } = props; + const { cookie } = parseCookie(req.headers, ctx.account); + + const requestOptions = { + body: data, + headers: { + accept: "application/json", + "content-type": "application/json", + cookie, + }, + }; + + const response = + await (isPrivateEntity + ? vcs[`POST /api/dataentities/:acronym/documents`]( + { acronym }, + requestOptions, + ) + : vcsDeprecated[`POST /api/dataentities/:acronym/documents`]( + { acronym }, + requestOptions, + )); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/newsletter/subscribe.ts b/vtex/actions/newsletter/subscribe.ts new file mode 100644 index 000000000..7195bbbba --- /dev/null +++ b/vtex/actions/newsletter/subscribe.ts @@ -0,0 +1,37 @@ +import { AppContext } from "../../mod.ts"; + +export interface Props { + email: string; + name?: string; + page?: string; + part?: string; + campaing?: string; +} + +const action = async ( + props: Props, + _req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const form = new FormData(); + const { + email, + name = "", + part = "newsletter", + page = "_", + campaing = "newsletter:opt-in", + } = props; + + form.append("newsletterClientName", name); + form.append("newsletterClientEmail", email); + form.append("newsInternalPage", page); + form.append("newsInternalPart", part); + form.append("newsInternalCampaign", campaing); + + await vcsDeprecated["POST /no-cache/Newsletter.aspx"]({}, { + body: form, + }); +}; + +export default action; diff --git a/vtex/actions/notifyme.ts b/vtex/actions/notifyme.ts new file mode 100644 index 000000000..07d626bd5 --- /dev/null +++ b/vtex/actions/notifyme.ts @@ -0,0 +1,28 @@ +import { AppContext } from "../mod.ts"; + +export interface Props { + email: string; + skuId: string; + name?: string; +} + +/** + * @docs https://developers.vtex.com/docs/api-reference/checkout-api#post-/api/checkout/pub/orderForm/-orderFormId-/items + */ +const action = async ( + props: Props, + _req: Request, + ctx: AppContext, +): Promise => { + const { vcsDeprecated } = ctx; + const form = new FormData(); + const { email, skuId, name = "" } = props; + + form.append("notifymeClientName", name); + form.append("notifymeClientEmail", email); + form.append("notifymeIdSku", skuId); + + await vcsDeprecated["POST /no-cache/AviseMe.aspx"]({}, { body: form }); +}; + +export default action; diff --git a/vtex/actions/payments/delete.ts b/vtex/actions/payments/delete.ts new file mode 100644 index 000000000..341ecee37 --- /dev/null +++ b/vtex/actions/payments/delete.ts @@ -0,0 +1,39 @@ +import { AppContext } from "../../mod.ts"; +import { parseCookie } from "../../utils/vtexId.ts"; + +export interface DeleteCard { + deletePaymentToken: boolean; +} + +interface Props { + id: string; +} + +async function loader( + { id }: Props, + req: Request, + ctx: AppContext, +): Promise { + const { io } = ctx; + const { cookie, payload } = parseCookie(req.headers, ctx.account); + + if (!payload?.sub || !payload?.userId) { + return null; + } + + const mutation = `mutation DeleteCreditCardToken($tokenId: ID!) { + deletePaymentToken(tokenId: $tokenId) @context(provider: "vtex.my-cards-graphql@2.x") + }`; + + try { + return await io.query({ + query: mutation, + variables: { tokenId: id }, + }, { headers: { cookie } }); + } catch (e) { + console.error(e); + return null; + } +} + +export default loader; diff --git a/vtex/actions/profile/newsletterProfile.ts b/vtex/actions/profile/newsletterProfile.ts new file mode 100644 index 000000000..1f14b6403 --- /dev/null +++ b/vtex/actions/profile/newsletterProfile.ts @@ -0,0 +1,60 @@ +import { AppContext } from "../../mod.ts"; +import { parseCookie } from "../../utils/vtexId.ts"; + +interface NewsletterInput { + email: string; + isNewsletterOptIn: boolean; +} + +const newsletterProfile = async ( + props: NewsletterInput, + req: Request, + ctx: AppContext, +): Promise => { + const { io } = ctx; + const { cookie } = parseCookie(req.headers, ctx.account); + + if (!props?.email) { + console.error("User profile not found or email is missing:", props.email); + return null; + } + + const mutation = ` + mutation SubscribeNewsletter($email: String!, $isNewsletterOptIn: Boolean!) { + subscribeNewsletter(email: $email, isNewsletterOptIn: $isNewsletterOptIn) + @context(provider: "vtex.store-graphql@2.x") + } + `; + + const variables = { + email: props.email, + isNewsletterOptIn: props.isNewsletterOptIn, + }; + + try { + await io.query<{ subscribeNewsletter: boolean }, unknown>( + { + query: mutation, + operationName: "SubscribeNewsletter", + variables, + }, + { + headers: { + cookie, + }, + }, + ); + + const result = await ctx.invoke("vtex/loaders/user.ts"); + const newsletterField = result?.customFields?.find((field) => + field.key === "isNewsletterOptIn" + ); + + return newsletterField?.value === "true"; + } catch (error) { + console.error("Error subscribing to newsletter:", error); + return null; + } +}; + +export default newsletterProfile; diff --git a/vtex/actions/profile/updateProfile.ts b/vtex/actions/profile/updateProfile.ts new file mode 100644 index 000000000..17c4f724b --- /dev/null +++ b/vtex/actions/profile/updateProfile.ts @@ -0,0 +1,93 @@ +import { AppContext } from "../../mod.ts"; +import { parseCookie } from "../../utils/vtexId.ts"; +import { Person } from "../../../commerce/types.ts"; +import type { User } from "../../loaders/user.ts"; + +export interface UserMutation { + firstName?: string; + lastName?: string; + email?: string; + homePhone?: string | null; + gender?: string | null; + birthDate?: string | null; + corporateName?: string | null; + tradeName?: string | null; + businessPhone?: string | null; + isCorporate?: boolean; +} + +const updateProfile = async ( + props: UserMutation, + req: Request, + ctx: AppContext, +): Promise => { + const { io } = ctx; + const { cookie } = parseCookie(req.headers, ctx.account); + + if (!props?.email) { + console.error("User profile not found or email is missing:", props.email); + return null; + } + const mutation = ` + mutation UpdateProfile($input: ProfileInput!) { + updateProfile(fields: $input) @context(provider: "vtex.store-graphql") { + cacheId + firstName + lastName + birthDate + gender + homePhone + businessPhone + document + email + tradeName + corporateName + corporateDocument + stateRegistration + isCorporate + } + } + `; + + try { + const { updateProfile: updatedUser } = await io.query< + { updateProfile: User }, + { input: UserMutation } + >( + { + query: mutation, + operationName: "UpdateProfile", + variables: { + input: { + ...props, + email: props.email, + }, + }, + }, + { headers: { cookie } }, + ); + + return { + "@id": updatedUser?.userId ?? updatedUser.id, + email: updatedUser.email, + givenName: updatedUser?.firstName, + familyName: updatedUser?.lastName, + taxID: updatedUser?.document?.replace(/[^\d]/g, ""), + gender: updatedUser?.gender === "female" + ? "https://schema.org/Female" + : "https://schema.org/Male", + telephone: updatedUser?.homePhone, + birthDate: updatedUser?.birthDate, + corporateName: updatedUser?.tradeName, + corporateDocument: updatedUser?.corporateDocument, + businessPhone: updatedUser?.businessPhone, + isCorporate: updatedUser?.isCorporate, + customFields: updatedUser?.customFields, + }; + } catch (error) { + console.error("Error updating user profile:", error); + return null; + } +}; + +export default updateProfile; diff --git a/vtex/actions/review/submit.ts b/vtex/actions/review/submit.ts new file mode 100644 index 000000000..a42f0b617 --- /dev/null +++ b/vtex/actions/review/submit.ts @@ -0,0 +1,44 @@ +import { getCookies } from "std/http/cookie.ts"; +import { AppContext } from "../../../vtex/mod.ts"; +import { VTEX_ID_CLIENT_COOKIE } from "../../utils/vtexId.ts"; + +export interface Props { + data: { + productId: string; + rating: number; + title: string; + text: string; + reviewerName: string; + approved: boolean; + }; +} + +// docs https://developers.vtex.com/docs/api-reference/reviews-and-ratings-api#post-/reviews-and-ratings/api/review?endpoint=post-/reviews-and-ratings/api/review + +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +) => { + const { data } = props; + const cookies = getCookies(req.headers); + const authCookie = cookies[VTEX_ID_CLIENT_COOKIE] || + cookies[`${VTEX_ID_CLIENT_COOKIE}_${ctx.account}`]; + + const requestOptions = { + body: data, + headers: { + "accept": "application/json", + "content-type": "application/json", + "VtexidClientAutCookie": authCookie, + }, + }; + + const response = await ( + ctx.my[`POST /reviews-and-ratings/api/review`]({}, requestOptions) + ); + + return response.json(); +}; + +export default action; diff --git a/vtex/actions/sessions/delete.ts b/vtex/actions/sessions/delete.ts new file mode 100644 index 000000000..e55b77e31 --- /dev/null +++ b/vtex/actions/sessions/delete.ts @@ -0,0 +1,39 @@ +import { AppContext } from "../../mod.ts"; +import { parseCookie } from "../../utils/vtexId.ts"; + +export interface DeleteSession { + logOutFromSession: string; +} + +interface Props { + sessionId: string; +} + +async function loader( + { sessionId }: Props, + req: Request, + ctx: AppContext, +): Promise { + const { io } = ctx; + const { cookie, payload } = parseCookie(req.headers, ctx.account); + + if (!payload?.sub || !payload?.userId) { + return null; + } + + const mutation = `mutation LogOutFromSession($sessionId: ID) { + logOutFromSession(sessionId: $sessionId) @context(provider: "vtex.store-graphql@2.x") + }`; + + try { + return await io.query({ + query: mutation, + variables: { sessionId }, + }, { headers: { cookie } }); + } catch (e) { + console.error(e); + return null; + } +} + +export default loader; diff --git a/vtex/actions/trigger.ts b/vtex/actions/trigger.ts new file mode 100644 index 000000000..a69b78828 --- /dev/null +++ b/vtex/actions/trigger.ts @@ -0,0 +1,46 @@ +import { AppContext } from "../mod.ts"; + +export interface VTEXNotificationPayload { + /** @description SKU ID in VTEX **/ + IdSku: string; + /** @description Seller’s account name in VTEX, shown in the store’s VTEX Admin url. **/ + An: string; + /** @description Affiliate ID generated automatically in the configuration. **/ + IdAffiliate: string; + /** @description Product ID in VTEX **/ + ProductId: number; + /** @description Date when the item was updated **/ + DateModified: string; + /** @description Identifies whether the product is active or not. In case it is “false”, it means the product was deactivated in VTEX and should be blocked in the marketplace. We recommend that the inventory level is zeroed in the marketplace, and the product is blocked. In case the marketplace doesn’t allow it to be deactivated, the product should be excluded, along with any existing correspondences in the connector. **/ + IsActive: boolean; + /** @description Identifies that the inventory level has been altered. Connectors should send an Fulfillment Simulation request to collect updated information. **/ + StockModified: boolean; + /** @description Identifies that the price has been altered. Connectors should send an Fulfillment Simulation request to collect updated information. **/ + PriceModified: boolean; + /** @description Identifies that the product/SKU registration data has changed, like name, description, weight, etc **/ + HasStockKeepingUnitModified: boolean; + /** @description Identifies that the product is no longer associated with the trade policy. In case the marketplace doesn’t allow it to be deactivated, the product should be excluded, along with any existing correspondences in the connector. **/ + HasStockKeepingUnitRemovedFromAffiliate: boolean; +} + +const action = async ( + props: VTEXNotificationPayload, + _req: Request, + ctx: AppContext, +): Promise<{ id: string }> => { + const { IdSku } = props; + + if (!IdSku) { + throw new Error("Missing idSKU"); + } + + const response = await ctx.invoke("workflows/actions/start.ts", { + // @ts-expect-error vtex trigger is on generated type + key: "vtex-trigger", + args: [props], + }); + + return { id: response.id }; +}; + +export default action; diff --git a/vtex/actions/wishlist/addItem.ts b/vtex/actions/wishlist/addItem.ts new file mode 100644 index 000000000..fdc2ce1d2 --- /dev/null +++ b/vtex/actions/wishlist/addItem.ts @@ -0,0 +1,37 @@ +import { AppContext } from "../../mod.ts"; +import type { WishlistItem } from "../../utils/types.ts"; +import { parseCookie } from "../../utils/vtexId.ts"; + +export interface Props { + productId: string; + sku: string; +} + +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { io } = ctx; + const { cookie, payload } = parseCookie(req.headers, ctx.account); + const user = payload?.sub; + + if (!user) { + return []; + } + + await io.query({ + operationName: "AddToWishlist", + variables: { + name: "Wishlist", + shopperId: user, + listItem: props, + }, + query: + `mutation AddToWishlist($listItem: ListItemInputType!, $shopperId: String!, $name: String!, $public: Boolean) { addToList(listItem: $listItem, shopperId: $shopperId, name: $name, public: $public) @context(provider: "vtex.wish-list@1.x") }`, + }, { headers: { cookie } }); + + return ctx.invoke.vtex.loaders.wishlist({ allRecords: true }); +}; + +export default action; diff --git a/vtex/actions/wishlist/removeItem.ts b/vtex/actions/wishlist/removeItem.ts new file mode 100644 index 000000000..ecdb51987 --- /dev/null +++ b/vtex/actions/wishlist/removeItem.ts @@ -0,0 +1,36 @@ +import wishlistLoader from "../../loaders/wishlist.ts"; +import { AppContext } from "../../mod.ts"; +import type { WishlistItem } from "../../utils/types.ts"; +import { parseCookie } from "../../utils/vtexId.ts"; + +export type Props = { id: string }; + +const action = async ( + props: Props, + req: Request, + ctx: AppContext, +): Promise => { + const { io } = ctx; + const { cookie, payload } = parseCookie(req.headers, ctx.account); + const user = payload?.sub; + const { id } = props; + + if (!user) { + return []; + } + + await io.query({ + operationName: "RemoveFromList", + variables: { + name: "Wishlist", + shopperId: user, + id, + }, + query: + `mutation RemoveFromList($id: ID!, $shopperId: String!, $name: String) { removeFromList(id: $id, shopperId: $shopperId, name: $name) @context(provider: "vtex.wish-list@1.x") }`, + }, { headers: { cookie } }); + + return wishlistLoader({ count: Infinity }, req, ctx); +}; + +export default action; diff --git a/vtex/components/VTEXPortalDataLayerCompatibility.tsx b/vtex/components/VTEXPortalDataLayerCompatibility.tsx new file mode 100644 index 000000000..d9ca176d3 --- /dev/null +++ b/vtex/components/VTEXPortalDataLayerCompatibility.tsx @@ -0,0 +1,200 @@ +import { type JSX } from "preact"; +import { Product } from "../../commerce/types.ts"; +import { useScriptAsDataURI } from "@deco/deco/hooks"; +declare global { + interface Window { + // deno-lint-ignore no-explicit-any + datalayer_product: any; + shelfProductIds: string[]; + skuJson: ProductSKUJsonProps; + dataLayer: unknown[]; + } +} +type ScriptProps = JSX.HTMLAttributes; +function addVTEXPortalDataSnippet(accountName: string) { + performance.mark("start-vtex-dl"); + const url = new URL(globalThis.window.location.href); + const structuredDataScripts = + document.querySelectorAll('script[type="application/ld+json"]') || []; + // deno-lint-ignore no-explicit-any + const structuredDatas: Record[] = []; + // deno-lint-ignore no-explicit-any + structuredDataScripts.forEach((v: any) => { + structuredDatas.push(JSON.parse(v.text)); + }); + const breadcrumbSD = structuredDatas.find((s) => + s["@type"] === "BreadcrumbList" + ); + performance.mark("end-sd"); + // deno-lint-ignore no-explicit-any + const getPageType = (structuredData: undefined | Record) => { + if (url.pathname === "/") { + return "homeView"; + } + const isProductPage = structuredDatas.some((s) => s["@type"] === "Product"); + if (isProductPage) { + return "productView"; + } + const isSearchPage = url.pathname === "/s"; + if (isSearchPage) { + return "internalSiteSearchView"; + } + if (structuredData?.itemList?.length === 1) { + return "departmentView"; + } + if (structuredData?.itemList?.length >= 2) { + return "categoryView"; + } + return "otherView"; + }; + const pageType = getPageType(breadcrumbSD); + // deno-lint-ignore no-explicit-any + const props: Record = { + pageCategory: "Home", + pageDepartment: null, + pageUrl: globalThis.window.location.href, + pageTitle: document.title, + skuStockOutFromShelf: [], + skuStockOutFromProductDetail: [], + accountName: `${accountName}`, + pageFacets: [], + shelfProductIds: [], + }; + const department = breadcrumbSD?.itemListElement?.[0]; + if (pageType === "productView") { + props.pageCategory = "Product"; + props.pageDepartment = department?.name || null; + const product = globalThis.window.datalayer_product || {}; + Object.assign(props, product); + } + if (pageType === "departmentView") { + props.pageCategory = "Department"; + props.pageDepartment = department?.name || null; + props.departmentName = department?.name || null; + props.categoryName = department?.name || null; + } + const category = breadcrumbSD?.itemListElement?.[1]; + if (pageType === "categoryView") { + props.pageCategory = "Category"; + props.pageDepartment = department?.name || null; + props.categoryName = category?.name || null; + } + if (pageType === "internalSiteSearchView") { + props.pageCategory = "InternalSiteSearch"; + props.siteSearchTerm = url.searchParams.get("q"); + } + if (pageType === "otherView") { + const pathNames = url.pathname.split("/").filter(Boolean); + props.pageCategory = pathNames.pop() || null; + } + props.shelfProductIds = globalThis.window.shelfProductIds || []; + globalThis.window.dataLayer = globalThis.window.dataLayer || []; + // VTEX Default position is first... + globalThis.window.dataLayer.unshift(props); + // But GTM handles .push function + globalThis.window.dataLayer.push(props); + globalThis.window.dataLayer.push({ event: pageType }); + performance.mark("end-vtex-dl"); + performance.measure("vtex-dl-qs-ld-json", "start-vtex-dl", "end-sd"); + performance.measure("vtex-dl-compat", "start-vtex-dl", "end-vtex-dl"); +} +interface AddVTEXPortalData extends ScriptProps { + accountName: string; +} +export function AddVTEXPortalData( + { accountName, ...props }: AddVTEXPortalData, +) { + return ( +