[{"data":1,"prerenderedAt":522},["ShallowReactive",2],{"bootstrap":3,"blog-proximity-based-shipping-with-commerce-layer-and-google":277},{"navigation":4,"globalConfig":196,"forms":218},{"headerSections":5,"headerFooterLinks":67,"footer":76,"legalLinks":153,"socialLinks":176},[6,39,44,50,57,62],{"hasSubMenu":7,"title":8,"links":9,"columns":38},true,"Product",[10,20,26,32],{"showTag":11,"_modelApiKey":12,"text":13,"subtitle":14,"icon":15,"isExternal":11,"link":16,"openForm":11},false,"navigation_link","Universal checkout","Sell anywhere","globe-simple",{"_modelApiKey":17,"slug":18,"title":19},"page","universal-checkout","Universal Checkout",{"showTag":11,"_modelApiKey":12,"text":21,"subtitle":22,"icon":23,"isExternal":11,"link":24,"openForm":11},"Distributed OMS","Manage orders","package",{"_modelApiKey":17,"slug":25,"title":21},"distributed-oms",{"showTag":11,"_modelApiKey":12,"text":27,"subtitle":28,"icon":29,"isExternal":11,"link":30,"openForm":11},"Promotion engine","Automated discounts","seal-percent",{"_modelApiKey":17,"slug":31,"title":27},"promotion-engine",{"showTag":11,"_modelApiKey":12,"text":33,"subtitle":34,"icon":35,"isExternal":11,"link":36,"openForm":11},"POS system","Operate stores","devices",{"_modelApiKey":17,"slug":37,"title":33},"pos-system",2,{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":40,"isExternal":11,"link":41,"openForm":11,"title":40},"Pricing",{"_modelApiKey":42,"slug":43},"pricing_page","pricing",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":45,"isExternal":11,"link":46,"openForm":11,"title":45},"Customers",{"_modelApiKey":47,"slug":48,"title":49},"customer_landing_page","customers","Customers page",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":51,"isExternal":11,"link":52,"openForm":11,"form":54,"title":51},"Partners",{"_modelApiKey":17,"slug":53,"title":51},"partners",{"_modelApiKey":55,"slug":53,"name":56},"form","PartnersForm",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":58,"label":59,"isExternal":7,"url":60,"openForm":11,"title":61},"Docs Hub","Learn more","https:\u002F\u002Fdocs.commercelayer.io\u002F","Docs",{"hasSubMenu":11,"showTag":11,"_modelApiKey":12,"text":63,"isExternal":11,"link":64,"openForm":11,"title":63},"Blog",{"_modelApiKey":65,"slug":66},"blog_landing_page","blog",[68,72],{"text":69,"link":70},"Sign in",{"_modelApiKey":12,"text":69,"isExternal":7,"url":71,"openForm":11},"https:\u002F\u002Fdashboard.commercelayer.io\u002Fsign_in",{"text":73,"link":74},"Sign up for free",{"_modelApiKey":12,"text":73,"isExternal":7,"url":75,"openForm":11},"https:\u002F\u002Fdashboard.commercelayer.io\u002Fsign_up",[77,89,109,132],{"hasSubMenu":7,"title":8,"links":78},[79,81,83,85,87],{"showTag":11,"_modelApiKey":12,"text":13,"subtitle":14,"icon":15,"isExternal":11,"link":80,"openForm":11},{"_modelApiKey":17,"slug":18,"title":19},{"showTag":11,"_modelApiKey":12,"text":21,"subtitle":22,"icon":23,"isExternal":11,"link":82,"openForm":11},{"_modelApiKey":17,"slug":25,"title":21},{"showTag":11,"_modelApiKey":12,"text":27,"subtitle":28,"icon":29,"isExternal":11,"link":84,"openForm":11},{"_modelApiKey":17,"slug":31,"title":27},{"showTag":11,"_modelApiKey":12,"text":33,"subtitle":34,"icon":35,"isExternal":11,"link":86,"openForm":11},{"_modelApiKey":17,"slug":37,"title":33},{"showTag":11,"_modelApiKey":12,"text":40,"isExternal":11,"link":88,"openForm":11},{"_modelApiKey":42,"slug":43},{"hasSubMenu":7,"title":90,"links":91},"Resources",[92,94,99,104,107],{"showTag":11,"_modelApiKey":12,"text":93,"isExternal":7,"url":60,"openForm":11},"Documentation",{"showTag":11,"_modelApiKey":12,"text":95,"label":95,"isExternal":11,"link":96,"openForm":11},"Agentic Commerce",{"_modelApiKey":17,"slug":97,"title":98},"agentic-commerce-whitepaper","Agentic Commerce Whitepaper",{"showTag":11,"_modelApiKey":12,"text":100,"isExternal":11,"link":101,"openForm":11},"Core concepts",{"_modelApiKey":17,"slug":102,"title":103},"headless-commerce","Headless commerce",{"showTag":11,"_modelApiKey":12,"text":105,"isExternal":7,"url":106,"openForm":11},"Changelog","https:\u002F\u002Fdocs.commercelayer.io\u002Fchangelog\u002F",{"showTag":11,"_modelApiKey":12,"text":63,"isExternal":11,"link":108,"openForm":11},{"_modelApiKey":65,"slug":66},{"hasSubMenu":7,"title":110,"links":111},"Company",[112,116,118,121,126],{"showTag":11,"_modelApiKey":12,"text":113,"isExternal":11,"link":114,"openForm":11},"Vision",{"_modelApiKey":17,"slug":115,"title":113},"vision",{"showTag":11,"_modelApiKey":12,"text":45,"isExternal":11,"link":117,"openForm":11},{"_modelApiKey":47,"slug":48,"title":49},{"showTag":11,"_modelApiKey":12,"text":51,"isExternal":11,"link":119,"openForm":11,"form":120},{"_modelApiKey":17,"slug":53,"title":51},{"_modelApiKey":55,"slug":53,"name":56},{"showTag":11,"_modelApiKey":12,"text":122,"isExternal":11,"link":123,"openForm":11},"Security",{"_modelApiKey":17,"slug":124,"title":125},"security","Security at Commerce Layer",{"showTag":11,"_modelApiKey":12,"text":127,"label":128,"isExternal":11,"openForm":7,"form":129},"Giveback","Giveback program",{"_modelApiKey":55,"slug":130,"name":131},"giveback","GivebackForm",{"hasSubMenu":7,"title":133,"links":134},"Get in touch",[135,140,145,149],{"showTag":11,"_modelApiKey":12,"text":136,"isExternal":11,"openForm":7,"form":137},"Contact",{"_modelApiKey":55,"slug":138,"name":139},"contact","ContactUsForm",{"showTag":11,"_modelApiKey":12,"text":141,"isExternal":11,"openForm":7,"form":142},"Book a demo",{"_modelApiKey":55,"slug":143,"name":144},"demo","RequestDemoForm",{"showTag":11,"_modelApiKey":12,"text":146,"label":147,"isExternal":7,"url":148,"openForm":11},"Support","Email support","mailto:support@commercelayer.io",{"showTag":11,"_modelApiKey":12,"text":150,"label":151,"isExternal":7,"url":152,"openForm":11},"Community","Discord community","https:\u002F\u002Fdiscord.gg\u002Fcommercelayer",{"hasSubMenu":7,"title":154,"links":155},"Legal",[156,161,165,169,173],{"showTag":11,"_modelApiKey":12,"text":157,"label":158,"isExternal":7,"url":159,"openForm":11,"htmlClassNames":160},"Terms","Terms and conditions","\u002Flegal-embed\u002Fterms-of-service","iubenda-nostyle no-brand iubenda-noiframe iubenda-embed",{"showTag":11,"_modelApiKey":12,"text":162,"label":163,"isExternal":7,"url":164,"openForm":11,"htmlClassNames":160},"Privacy","Privacy policy","\u002Flegal-embed\u002Fprivacy-policy",{"showTag":11,"_modelApiKey":12,"text":166,"label":167,"isExternal":7,"url":168,"openForm":11,"htmlClassNames":160},"Cookies","Cookie policy","\u002Flegal-embed\u002Fcookie-policy",{"showTag":11,"_modelApiKey":12,"text":170,"isExternal":7,"url":171,"openForm":11,"htmlClassNames":172},"Notice at collection","#","iubenda-cs-uspr-link",{"showTag":11,"_modelApiKey":12,"text":174,"isExternal":7,"url":171,"openForm":11,"htmlClassNames":175},"Privacy preferences","iubenda-cs-preferences-link",[177,180,184,188,192],{"title":178,"type":179,"url":152,"isExternal":7},"Discord","discord-logo",{"title":181,"type":182,"url":183,"isExternal":7},"Github","github","https:\u002F\u002Fgithub.com\u002Fcommercelayer",{"title":185,"type":186,"url":187,"isExternal":7},"Bluesky","bluesky-logo","https:\u002F\u002Fbsky.app\u002Fprofile\u002Fcommercelayer.io",{"title":189,"type":190,"url":191,"isExternal":7},"X","x-logo","https:\u002F\u002Fx.com\u002Fcommercelayer",{"title":193,"type":194,"url":195,"isExternal":7},"LinkedIn","linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fcommerce-layer",{"pagesWithoutCtaPanel":197,"ctaPanel":198,"notification":208,"showApiResponseTime":11,"newsletterSignup":216},[],{"title":199,"subtitle":200,"ctas":201},"Ready to get started?","Discover how we can help your business.",[202,205],{"_modelApiKey":203,"text":204,"isExternal":7,"url":75,"openForm":11},"link","Get started for free",{"_modelApiKey":203,"text":206,"isExternal":11,"openForm":7,"form":207},"Contact us",{"_modelApiKey":55,"slug":138,"name":139},{"showNotification":7,"link":209,"linkText":210,"text":215},{"_modelApiKey":203,"text":210,"isExternal":11,"link":211,"openForm":11},"Read article",{"_modelApiKey":212,"slug":213,"title":214},"blog_post","payment-api-redesigned","Introducing our new Payments API. For humans and agents.","\u003Cp>Introducing our new Payments API. For humans and agents.\u003C\u002Fp>\n",{"ctaText":217},"Subscribe",[219,225,229,236,240,246,249,253,261,262,267,272,274],{"_modelApiKey":55,"slug":220,"pardotFormEmbedCode":221,"formEmbedMinHeight":222,"successTitle":223,"successCopy":224},"agentic-commerce","https:\u002F\u002Fcommercelayer.fillout.com\u002Ft\u002F5w4Khwgxr6us?whitepaper=agentic",520,"Thank you for your request!","We’ll be in touch soon to arrange a date and time for your personalized demo.",{"_modelApiKey":55,"slug":226,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"successTitle":223,"successCopy":224},"shopify","https:\u002F\u002Fform.fillout.com\u002Ft\u002FgVnLSmKZwRus",850,{"_modelApiKey":55,"slug":130,"pardotFormEmbedCode":230,"formEmbedMinHeight":231,"displayTitle":232,"copy":233,"successTitle":234,"successCopy":235},"https:\u002F\u002Fform.fillout.com\u002Ft\u002FmVDF8aAiofus",500,"We give back.","\u003Cp>Part of Commerce Layer&#39;s company ethos is to empathize and support one another: co-workers, customers, and community. We want to do our part to help nonprofit organizations and small businesses who may feel adversely affected by the current global economic climate, and as a result, need in-kind support to reach their customers.\u003C\u002Fp>\n\n\u003Cp>If this general description matches your organization’s needs, please tell us your story by completing the following form so we can consider helping your cause.\u003C\u002Fp>\n","Thank you!","We will contact you soon to learn more about your story.",{"_modelApiKey":55,"slug":237,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"displayTitle":238,"copy":239},"book-an-onboarding-session","Book an onboarding session","\u003Cp>Schedule some time and our devs will help you set up a Commerce Layer + Sanity store.\u003C\u002Fp>\n",{"_modelApiKey":55,"slug":241,"pardotFormEmbedCode":242,"formEmbedMinHeight":243,"displayTitle":244,"successTitle":234,"successCopy":245},"download-our-whitepaper","https:\u002F\u002Fform.fillout.com\u002Ft\u002F5YPWmy8oT7us?whitepaper=ComposableCommerce",450,"Download our whitepaper.","Check your inbox. You should receive a download link soon.",{"_modelApiKey":55,"slug":247,"pardotFormEmbedCode":248,"formEmbedMinHeight":243,"displayTitle":244,"successTitle":234,"successCopy":245},"download-the-content-and-commerce-whitepaper","https:\u002F\u002Fform.fillout.com\u002Ft\u002F5YPWmy8oT7us?whitepaper=ContentAndCommerce",{"_modelApiKey":55,"slug":250,"pardotFormEmbedCode":251,"formEmbedMinHeight":243,"displayTitle":244,"copy":252,"successTitle":234,"successCopy":245},"whitepaper","https:\u002F\u002Fform.fillout.com\u002Ft\u002F5YPWmy8oT7us?whitepaper=Jamstack","\u003Cp>Commerce Layer is the perfect platform for building commerce on the Jamstack. Convert any design into pixel perfect HTML pages. Connect a best-of-breed headless CMS. Go serverless and forget the hassle of managing complex infrastructures.\u003C\u002Fp>\n",{"_modelApiKey":55,"slug":254,"pardotFormEmbedCode":255,"formEmbedMinHeight":256,"displayTitle":257,"copy":258,"successTitle":259,"successCopy":260},"contact-links","https:\u002F\u002Fform.fillout.com\u002Ft\u002FeWPyhHGHT7us",750,"Contact us.","\u003Cp>Tell us about your project. Our team will reach out to you for a personalized offer.\u003C\u002Fp>\n","Thanks for your message!","We’ll be in touch soon to discuss your project details.",{"_modelApiKey":55,"slug":138,"pardotFormEmbedCode":255,"formEmbedMinHeight":256,"displayTitle":257,"copy":258,"successTitle":259,"successCopy":260},{"_modelApiKey":55,"slug":263,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"displayTitle":264,"successTitle":265,"successCopy":266},"demo-embed","Want to see it in action? Request a demo.","Thanks for your request!","We’ll be in touch soon to arrange a date and time for your free live demo.",{"_modelApiKey":55,"slug":53,"pardotFormEmbedCode":268,"formEmbedMinHeight":228,"displayTitle":269,"copy":270,"successTitle":234,"successCopy":271},"https:\u002F\u002Fform.fillout.com\u002Ft\u002F9MTkDWMUSqus","Become our partner.","\u003Cp>Join us and get our guidance in helping your clients succeed. Our partners are our ambassadors. We are committed to your success as much as you are.\u003C\u002Fp>\n","We will contact you soon to discuss the details of our partnership.",{"_modelApiKey":55,"slug":273,"pardotFormEmbedCode":268,"formEmbedMinHeight":228,"displayTitle":269,"copy":270,"successTitle":234,"successCopy":271},"partners-embed",{"_modelApiKey":55,"slug":143,"pardotFormEmbedCode":227,"formEmbedMinHeight":228,"displayTitle":275,"copy":276,"successTitle":265,"successCopy":266},"Get a free live demo.","\u003Cp>Get a personalized demo and learn more about what Commerce Layer can do for your company.\u003C\u002Fp>\n",{"_modelApiKey":212,"link":278,"subtitle":283,"date":283,"author":284,"additionalAuthors":294,"icon":295,"abstract":296,"tag":297,"displayTitle":279,"cardRatio":300,"featured":11,"seoMetaTags":301,"layout":372,"blogPostCarousel":303,"_seoMetaTags":479,"authors":517},{"text":279,"isExternal":11,"url":280,"link":281},"Streamline shipping with a proximity-based service that uses Commerce Layer and Google Maps.","",{"_modelApiKey":212,"slug":282},"proximity-based-shipping-with-commerce-layer-and-google","September 10, 2024",{"name":285,"picture":286},"Fabrizio Picca",{"url":287,"alt":288,"title":288,"width":289,"height":289,"format":290,"focalPoint":291,"customData":293},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1614875259-commercelayeravatarsquare113f26.png","Fabrizio Picca —      Partnerships Manager at Commerce Layer",1280,"png",{"x":292,"y":292},0.5,{},[],"map-pin","In this blog post, we present a proximity-based shipping service using Commerce Layer and Google Maps. This approach ensures faster deliveries and optimized costs by dynamically selecting the nearest stock location to the customer's address.",{"color":298,"name":299},"gray","Tutorials","portrait",[302,306,310,313,317,320,323,327,331,335,339,342,345,349,353,357,361,364,368],{"attributes":303,"tag":304,"content":305},null,"title","Proximity-based shipping with Google Maps ",{"attributes":307,"tag":309,"content":303},{"property":308,"content":305},"og:title","meta",{"attributes":311,"tag":309,"content":303},{"name":312,"content":305},"twitter:title",{"attributes":314,"tag":309,"content":303},{"name":315,"content":316},"description","How to streamline your shipping with a proximity-based service that uses Commerce Layer and Google Maps ",{"attributes":318,"tag":309,"content":303},{"property":319,"content":316},"og:description",{"attributes":321,"tag":309,"content":303},{"name":322,"content":316},"twitter:description",{"attributes":324,"tag":309,"content":303},{"property":325,"content":326},"og:image","https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1725956441-cl_proximity-based-shipping-02.jpg?auto=format&fit=max&w=1200",{"attributes":328,"tag":309,"content":303},{"property":329,"content":330},"og:image:width","1200",{"attributes":332,"tag":309,"content":303},{"property":333,"content":334},"og:image:height","630",{"attributes":336,"tag":309,"content":303},{"property":337,"content":338},"og:image:alt","The image shows shipping units surrounded by concentric circles with location markers to convey how Commerce Layer can enable proximity-based shipping.",{"attributes":340,"tag":309,"content":303},{"name":341,"content":326},"twitter:image",{"attributes":343,"tag":309,"content":303},{"name":344,"content":338},"twitter:image:alt",{"attributes":346,"tag":309,"content":303},{"property":347,"content":348},"og:locale","en",{"attributes":350,"tag":309,"content":303},{"property":351,"content":352},"og:type","article",{"attributes":354,"tag":309,"content":303},{"property":355,"content":356},"og:site_name","Commerce Layer",{"attributes":358,"tag":309,"content":303},{"property":359,"content":360},"article:modified_time","2025-10-08T16:22:49Z",{"attributes":362,"tag":309,"content":303},{"property":363,"content":280},"article:publisher",{"attributes":365,"tag":309,"content":303},{"name":366,"content":367},"twitter:card","summary_large_image",{"attributes":369,"tag":309,"content":303},{"name":370,"content":371},"twitter:site","@commercelayer",[373,379,384,387,399,402,411,414,424,428,429,432,434,437,440,443,445,448,450,453,455,458,461,463,466,476],{"_modelApiKey":374,"copy":375,"listStyle":376,"darkTheme":11,"buttons":377,"color":378,"indent":7},"wysiwyg","\u003Cp>Efficient shipping is a cornerstone of ecommerce success. Traditional methods relying on a single fulfillment center often lead to delays and higher costs. In this blog post, we&#39;ll explore a solution to this problem: a proximity-based shipping service using Commerce Layer and Google Maps. This approach ensures faster deliveries and optimized costs by dynamically selecting the nearest stock location to the customer&#39;s address. This example is designed to run on Deno, a modern runtime for JavaScript and TypeScript that offers enhanced security and performance features. Let&#39;s dive in.\u003C\u002Fp>\n\n\u003Ch4>The challenge: Optimal shipping routes.\u003C\u002Fh4>\n\n\u003Cp>Shipping products quickly and cost-effectively is a significant challenge for online retailers. Fulfillment from a single warehouse often results in inefficient shipping routes, longer delivery times, and increased expenses. Our proximity-based shipping service addresses this issue by selecting the closest stock location to the customer&#39;s address, ensuring quicker deliveries and lower costs.\u003C\u002Fp>\n\n\u003Ch4>Getting started: What you need.\u003C\u002Fh4>\n\n\u003Cp>To implement this solution, you will need:\u003C\u002Fp>\n\n\u003Col>\n\u003Cli>\u003Cstrong>Commerce Layer Account\u003C\u002Fstrong>: Sign up and set up your organization.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Google Maps API Key\u003C\u002Fstrong>: Register for the Google Maps API and obtain an API key.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Deno Environment\u003C\u002Fstrong>: Set up a Deno environment to run the code.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Dependencies\u003C\u002Fstrong>: Use \u003Ccode>import\u003C\u002Fcode> statements to include necessary modules.\u003C\u002Fli>\n\u003C\u002Fol>\n","directional",[],"red",{"_modelApiKey":380,"code":381,"showLineNumbers":11,"highlightLines":280,"language":382,"size":383},"code_block","import {\n  Address,\n  CommerceLayer,\n  Order,\n} from \"npm:@commercelayer\u002Fsdk\";\nimport { authentication } from \"npm:@commercelayer\u002Fjs-auth\";\nimport { Client as GoogleMapsClient } from \"npm:@googlemaps\u002Fgoogle-maps-services-js\";\nimport { oakCors } from \"oakCors\";\nimport { Application } from \"Application\";\nimport { Router } from \"Router\";","typescript","Standard",{"_modelApiKey":374,"copy":385,"listStyle":376,"darkTheme":11,"buttons":386,"color":378,"indent":11},"\u003Ch4>Architecture of the POC.\u003C\u002Fh4>\n\n\u003Cp>The architecture is quite straight forward. We are going to trigger a server function containing the proximity shipping logic using the commerce layer weboohks.\u003C\u002Fp>\n",[],{"_modelApiKey":388,"border":11,"caption":389,"image":390,"size":398,"darkTheme":11,"rounded":7,"color":378},"image_block","Proximity-based shipping architecture",{"url":391,"alt":392,"title":389,"width":393,"height":394,"format":395,"focalPoint":396,"customData":397},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1725614788-proximity-20shipping-20-20poc-20architecture.jpg","The image shows a simple diagram of a proximity-based shipping architecture. One the left, we see an order placed with Commerce Layer. The top of the diagram shows an order approval event web hook. The right side of the image shows a server callback. And the bottom shows the shipment API.",8995,5447,"jpg",{"x":292,"y":292},{},"standard",{"_modelApiKey":374,"copy":400,"listStyle":376,"darkTheme":11,"buttons":401,"color":378,"indent":11},"\u003Ch4>Setting up Commerce Layer.\u003C\u002Fh4>\n\n\u003Cp>In the Webhooks app, create a new webhook for the \u003Ccode>orders.start_fulfilling\u003C\u002Fcode> event.\u003C\u002Fp>\n",[],{"_modelApiKey":388,"border":7,"caption":403,"image":404,"size":398,"darkTheme":11,"rounded":7,"color":378},"The Commerce Layer Webhooks App",{"url":405,"alt":406,"title":403,"width":407,"height":408,"format":290,"focalPoint":409,"customData":410},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1725614999-untitled.png","The image shows the UI for the Commerce Layer Dashboard, with the Webhooks App in focus.",2694,930,{"x":292,"y":292},{},{"_modelApiKey":374,"copy":412,"listStyle":376,"darkTheme":11,"buttons":413,"color":378,"indent":11},"\u003Cp>Below is a screenshot of the configuration of the webhook. Notice the topic we are going to use and the includes that we need, in this case the shipping address of the order and, of course, the line items. Learn more about Commerce Layer \u003Ca target=\"_blank\" href=\"https:\u002F\u002Fdocs.commercelayer.io\u002Fcore\u002Freal-time-webhooks\">real-time webhooks here.\u003C\u002Fa> The Callback URL setting should contain your endpoint. In this POC, it is my Deno-deployed function.\u003C\u002Fp>\n",[],{"_modelApiKey":388,"border":7,"caption":415,"image":416,"size":398,"darkTheme":11,"rounded":7,"color":378},"Webhook configuration in the Commerce Layer web hook app.",{"url":417,"alt":418,"title":419,"width":420,"height":421,"format":290,"focalPoint":422,"customData":423},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1725615074-screenshot-202024-08-02-20at-2011-39-14.png","The image shows a webhook configuration in the Commerce Layer web hook app.","Commerce Layer Webhook Configuration",1266,2222,{"x":292,"y":292},{},{"_modelApiKey":374,"copy":425,"listStyle":426,"darkTheme":11,"buttons":427,"color":378,"indent":11},"\u003Ch4>Deep dive into the code\u003C\u002Fh4>\n\n\u003Cp>Here’s a detailed breakdown of the code that implements the proximity-based shipping service:\u003C\u002Fp>\n\n\u003Cul>\n\u003Cli>\u003Cstrong>Importing dependencies.\u003C\u002Fstrong>\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cp>We start by importing the essential modules from Commerce Layer SDK, Google Maps API, and other supporting libraries.\u003C\u002Fp>\n","checkbox",[],{"_modelApiKey":380,"code":381,"showLineNumbers":11,"highlightLines":280,"language":382,"size":383},{"_modelApiKey":374,"copy":430,"listStyle":426,"darkTheme":11,"buttons":431,"color":378,"indent":11},"\u003Cul>\n\u003Cli>\u003Cstrong>Setting up constants and clients.\u003C\u002Fstrong>\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cp>We define constants for the API endpoints, Commerce Layer credentials, and Google Maps API key. Then, we initialize the clients for Commerce Layer and Google Maps.\u003C\u002Fp>\n",[],{"_modelApiKey":380,"code":433,"showLineNumbers":11,"highlightLines":280,"language":382,"size":383},"const ENDPOINT = \"\u002Fapi\u002Fv1\u002FproximityShippingService\";\nconst CL_CLIENT_ID = \"YOUR_CL_CLIENT_ID\";\nconst CL_CLIENT_SECRET = \"YOUR_CL_CLIENT_SECRET\";\nconst CL_ORG_SLUG = \"proximity-shipping-demo\";\nconst GOOGLE_MAPS_KEY = \"YOUR_GOOGLE_MAPS_KEY\";\n\nconst gmClient = new GoogleMapsClient({});",{"_modelApiKey":374,"copy":435,"listStyle":426,"darkTheme":11,"buttons":436,"color":378,"indent":11},"\u003Cul>\n\u003Cli>\u003Cstrong>Authentication and initialization.\u003C\u002Fstrong>\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cp>We authenticate with Commerce Layer using client credentials and initialize the Commerce Layer client with the obtained access token.\u003C\u002Fp>\n",[],{"_modelApiKey":380,"code":438,"showLineNumbers":11,"highlightLines":280,"language":439,"size":383},"const auth = await authenticate('client_credentials', {\n  clientId: CL_CLIENT_ID,\n  clientSecret: CL_CLIENT_SECRET,\n})\n\nconst cl = CommerceLayer({\n  organization: CL_ORG_SLUG,\n  accessToken: token.accessToken,\n});","javascript",{"_modelApiKey":374,"copy":441,"listStyle":426,"darkTheme":11,"buttons":442,"color":378,"indent":11},"\u003Cul>\n\u003Cli>\u003Cstrong>Setting up the router.\u003C\u002Fstrong>\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cp>We set up a router using Oak to handle HTTP requests and enable CORS for the defined endpoint.\u003C\u002Fp>\n",[],{"_modelApiKey":380,"code":444,"showLineNumbers":11,"highlightLines":280,"language":382,"size":383},"const router = new Router();\nrouter.options(ENDPOINT, oakCors());",{"_modelApiKey":374,"copy":446,"listStyle":426,"darkTheme":11,"buttons":447,"color":378,"indent":11},"\u003Cul>\n\u003Cli>\u003Cstrong>Fetching the stock locations.\u003C\u002Fstrong>\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cp>We fetch the stock locations from Commerce Layer, including their addresses.\u003C\u002Fp>\n",[],{"_modelApiKey":380,"code":449,"showLineNumbers":11,"highlightLines":280,"language":382,"size":383},"const stock_locations = await cl.stock_locations.list({\n  include: [\"address\"],\n});",{"_modelApiKey":374,"copy":451,"listStyle":426,"darkTheme":11,"buttons":452,"color":378,"indent":11},"\u003Cul>\n\u003Cli>\u003Cstrong>Handling webhook requests and calculating distances.\u003C\u002Fstrong>\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cp>We handle POST requests to the defined endpoint, process the order data, and calculate the nearest stock location using the Google Maps Distance Matrix API.\u003C\u002Fp>\n",[],{"_modelApiKey":380,"code":454,"showLineNumbers":11,"highlightLines":280,"language":382,"size":383},"router.post(ENDPOINT, oakCors(), async (conn) => {\n  if (!conn.request.hasBody) {\n    conn.throw(415);\n  }\n\n  const body = await conn.request.body().value;\n\n  const order: Order = { ...body.data.attributes, id: body.data.id };\n\n  const inclusions: Array\u003Cany> = body.included;\n\n  const shippingAddress: Address = inclusions.find(\n    (inclusion) =>\n      inclusion.id === body.data.relationships.shipping_address.data.id\n  ).attributes;\n\n  if (shippingAddress) {\n    const shippingAddressAsString = shippingAddress.full_address;\n\n    if (shippingAddressAsString) {\n      try{\n        const result = await gmClient.distancematrix({\n          params: {\n            destinations: [shippingAddressAsString],\n            origins: stock_locations.map(\n              (stock_location) =>\n                `${stock_location.address?.line_1},\n              ${stock_location.address?.zip_code} ${stock_location.address?.city},\n              ${stock_location.address?.country_code}`\n            ),\n            key: GOOGLE_MAPS_KEY,\n          },\n        });\n\n        const closestStockLocation = result.data.rows\n          .map((row, index) => {\n            return {\n              ...stock_locations[index],\n              distance:\n                row.elements[0].status === \"ZERO_RESULTS\"\n                  ? -1\n                  : row.elements[0].distance.value,\n            };\n          })\n          .filter((result) => result.distance != -1)\n          .sort((a, b) => a.distance - b.distance)[0];\n\n        \u002F\u002Fmake sure we have a stock location\n        \u002F\u002FTODO if closest == current do nothing\n        if (closestStockLocation) { \n          const line_items = inclusions\n            .filter((inclusion) => inclusion.type === 'line_items')\n            .filter((line_item) => line_item.attributes.item_type === 'skus')\n\n          \u002F\u002Fget line items from shipment\n          const shipments = await cl.shipments.list({\n            filters: {\n              order_id_eq: order.id,\n            },\n            include: [\"stock_line_items\", \"available_shipping_methods\"],\n          });\n\n          \u002F\u002Fdelete current shipments\n\n          console.log(\n            `order ${order.id} has ${shipments.length} shipment(s) automatically generated`\n          );\n          shipments.forEach((shipment) => {\n            console.log(`deleting shipment ${shipment.id}`);\n            if (shipment.stock_line_items) {\n              shipment.stock_line_items.forEach((stock_line_item) => {\n                if (stock_line_item.line_item) {\n                  \u002F\u002Fstoring line items from stock_line_items\n                  console.log(\"storing line item \");\n                  \u002F\u002Fline_items.push(stock_line_item.line_item);\n                }\n              });\n            }\n            cl.shipments.delete(shipment.id);\n          });\n          console.log(`all shipments deleted for order ${order.id}`);\n          \u002F*TODO considering that stock has been released we should also update\n         stock data in the original stock location*\u002F\n\n          \u002F\u002Fcreate a new shipment from the closest stock location\n          console.log(`creating new shipment using proximity shipping`);\n\n          const newInventoryStockLocation = (\n            await cl.inventory_stock_locations.list({\n              filters: { stock_location_id_eq: closestStockLocation.id },\n            })\n          ).first();\n\n          if (\n            newInventoryStockLocation &&\n            shipments?.[0].available_shipping_methods?.[0].id != null\n          ) {\n            const newShipment = await cl.shipments\n              .create({\n                order: cl.orders.relationship(order.id),\n                inventory_stock_location:\n                  cl.inventory_stock_locations.relationship(\n                    newInventoryStockLocation.id\n                  ),\n                shipping_method: cl.shipping_methods.relationship(\n                  shipments[0].available_shipping_methods[0].id\n                ),\n              })\n              .catch((e) => {\n                console.error(e);\n                throw e;\n              });\n\n            await cl.shipments.update({\n              id: newShipment.id,\n              _upcoming: true,\n            });\n\n            await cl.shipments.update({\n              id: newShipment.id,\n              _picking: true,\n            });\n\n            console.log(\n              `shipment ${newShipment.id} created and attached to order ${order.id} with stock location in ${closestStockLocation.address?.city}`\n            );\n\n            \u002F\u002Fcreate stock line items and link them to the shipment\n            line_items.forEach(async (line_item) => {\n              if (line_item.sku_code) {\n                console.log(\"gettting stock item\");\n                const newStockItem = (\n                  await cl.stock_items.list({\n                    filters: {\n                      sku_code_eq: line_item.sku_code,\n                      stock_location_id_eq: closestStockLocation.id,\n                    },\n                  })\n                ).first();\n\n                if (newStockItem) {\n                  console.log(\"creating new stock line item \");\n                  await cl.stock_line_items\n                    .create({\n                      quantity: line_item.quantity,\n                      sku_code: line_item.sku_code,\n                      shipment: cl.shipments.relationship(newShipment.id),\n                      line_item: cl.line_items.relationship(line_item.id),\n                      stock_item: cl.stock_items.relationship(newStockItem.id),\n                    })\n                    .catch((e) => {\n                      console.error(e);\n                      throw e;\n                    });\n                }\n              }\n            });\n\n            await cl.attachments.create({\n              attachable: {id: order.id, type: 'orders'},\n              name: 'System',\n              description: `proximity shipping: closest shipping location to destination is ${closestStockLocation.id} ${closestStockLocation.address?.full_address}`,\n              reference_origin: 'app-shipments--note'\n            })\n\n            \u002F\u002Freturn 200\n            conn.response.type = \"json\";\n            conn.response.body = {\n              success: true\n            };\n          }\n        }\n      } catch (err){\n        console.log(err)\n      }  \n    }\n  } else {\n    conn.response.body = {\n      success: false,\n      error: {\n        code: 404,\n        message: `no shipping address in order ${body.data.attributes.id}`,\n      },\n    };\n  }\n});",{"_modelApiKey":374,"copy":456,"listStyle":376,"darkTheme":11,"buttons":457,"color":378,"indent":11},"\u003Cp>This is the core part of our POC, where we do the actual calculation of the distances and define the “closest” shipping location to the shipping address. Code here is not optimal for sure, but it’s just to give a sense of how this could work.\u003C\u002Fp>\n\n\u003Cp>This piece of code performs the following:\u003C\u002Fp>\n\n\u003Col>\n\u003Cli>Get shipping and stock locations address.\u003C\u002Fli>\n\u003Cli>Find the closest stock location using Google Distance Matrix API.\u003C\u002Fli>\n\u003Cli>If found, delete all current shipments. \u003C\u002Fli>\n\u003Cli>Recreate shipments from the closest stock location.\u003C\u002Fli>\n\u003Cli>Return ok.\u003C\u002Fli>\n\u003C\u002Fol>\n\n\u003Cp>You can find more information \u003Ca target=\"_blank\" href=\"https:\u002F\u002Fdevelopers.google.com\u002Fmaps\u002Fdocumentation\u002Fdistance-matrix\u002Foverview\">about the Google Distance Matrix API here\u003C\u002Fa>.\u003C\u002Fp>\n",[],{"_modelApiKey":374,"copy":459,"listStyle":426,"darkTheme":11,"buttons":460,"color":378,"indent":11},"\u003Cul>\n\u003Cli>\u003Cstrong>Starting the server.\u003C\u002Fstrong>\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cp>Finally, we create an Oak application, register the router, and start the server on port 8000.\u003C\u002Fp>\n",[],{"_modelApiKey":380,"code":462,"showLineNumbers":11,"highlightLines":280,"language":382,"size":383},"const server = new Application();\n\nserver.use(router.routes());\nserver.use(router.allowedMethods());\n\nawait server.listen({ port: 8000 });",{"_modelApiKey":374,"copy":464,"listStyle":376,"darkTheme":11,"buttons":465,"color":378,"indent":11},"\u003Ch3>The result.\u003C\u002Fh3>\n\n\u003Cp>Here is screenshot of the output from our proximity shipping custom logic:\u003C\u002Fp>\n",[],{"_modelApiKey":388,"border":7,"caption":467,"image":468,"size":398,"darkTheme":11,"rounded":7,"color":378},"Shipping based on proximity shown in the timeline in Commerce Layer's dashboard.",{"url":469,"alt":470,"title":471,"width":472,"height":473,"format":290,"focalPoint":474,"customData":475},"https:\u002F\u002Fwww.datocms-assets.com\u002F35053\u002F1725624401-untitled-2.png","Timeline view in the Commerce Layer dashboard that shows shipping based on proximity of the user to the stock location.","Proximity Shipping",1516,1428,{"x":292,"y":292},{},{"_modelApiKey":374,"copy":477,"listStyle":376,"darkTheme":11,"buttons":478,"color":378,"indent":11},"\u003Cp>In this order the original shipping location was different. Our custom strategy adjusted the shipment with the new stock location, closer to the order shipping address.\u003C\u002Fp>\n\n\u003Ch4>Conclusion: Elevate your shipping strategy.\u003C\u002Fh4>\n\n\u003Cp>Implementing a proximity-based shipping service can significantly enhance your ecommerce operations. By dynamically selecting the nearest stock location, you ensure faster delivery times and reduced shipping costs. This approach not only improves customer satisfaction, but also optimizes logistics, making your business more efficient and competitive. Leverage Commerce Layer&#39;s robust APIs and Google&#39;s distance calculation services today to transform your shipping process into a strategic advantage.\u003C\u002Fp>\n",[],[480,481,483,485,487,489,491,493,495,497,499,501,503,505,507,509,511,513,515],{"attributes":303,"tag":304,"content":305},{"attributes":482,"tag":309,"content":303},{"property":308,"content":305},{"attributes":484,"tag":309,"content":303},{"name":312,"content":305},{"attributes":486,"tag":309,"content":303},{"name":315,"content":316},{"attributes":488,"tag":309,"content":303},{"property":319,"content":316},{"attributes":490,"tag":309,"content":303},{"name":322,"content":316},{"attributes":492,"tag":309,"content":303},{"property":325,"content":326},{"attributes":494,"tag":309,"content":303},{"property":329,"content":330},{"attributes":496,"tag":309,"content":303},{"property":333,"content":334},{"attributes":498,"tag":309,"content":303},{"property":337,"content":338},{"attributes":500,"tag":309,"content":303},{"name":341,"content":326},{"attributes":502,"tag":309,"content":303},{"name":344,"content":338},{"attributes":504,"tag":309,"content":303},{"property":347,"content":348},{"attributes":506,"tag":309,"content":303},{"property":351,"content":352},{"attributes":508,"tag":309,"content":303},{"property":355,"content":356},{"attributes":510,"tag":309,"content":303},{"property":359,"content":360},{"attributes":512,"tag":309,"content":303},{"property":363,"content":280},{"attributes":514,"tag":309,"content":303},{"name":366,"content":367},{"attributes":516,"tag":309,"content":303},{"name":370,"content":371},[518],{"name":285,"picture":519},{"url":287,"alt":288,"title":288,"width":289,"height":289,"format":290,"focalPoint":520,"customData":521},{"x":292,"y":292},{},1790330005742]