### Install Medusa Application Source: https://docs.medusajs.com/resources/integrations/guides/sentry Installs a new Medusa application with the Next.js Starter Storefront. Follow the prompts for project name and storefront installation. ```bash ❯npx create-medusa-app@latest ``` -------------------------------- ### Start Strapi in Development Mode Source: https://docs.medusajs.com/resources/integrations/guides/strapi Starts the Strapi server in development mode, which automatically opens the admin panel setup page in your browser. ```bash npm run dev ``` -------------------------------- ### Install Meilisearch InstantSearch Packages Source: https://docs.medusajs.com/resources/integrations/guides/meilisearch Installs the necessary Meilisearch JavaScript and React InstantSearch libraries for the storefront. ```bash yarn add @meilisearch/instant-meilisearch react-instantsearch ``` -------------------------------- ### Install react-markdown Package Source: https://docs.medusajs.com/resources/integrations/guides/strapi Install the react-markdown package to render markdown content. This is necessary for displaying product descriptions from Strapi. ```bash yarn add react-markdown ``` -------------------------------- ### Start Strapi Server Source: https://docs.medusajs.com/resources/integrations/guides/strapi Command to start the Strapi development server. Ensure you are in the Strapi application's directory. ```bash yarn develop ``` -------------------------------- ### Install Medusa Application Source: https://docs.medusajs.com/resources/integrations/guides/segment Installs a new Medusa application with the Next.js Starter Storefront. This command sets up the backend and storefront in a monorepository structure. ```bash ❯npx create-medusa-app@latest ``` -------------------------------- ### Install Strapi Client Source: https://docs.medusajs.com/resources/integrations/guides/strapi Installs the Strapi client in a Medusa application to enable interaction with Strapi's API. ```bash npm install @strapi/client ``` -------------------------------- ### Install Algolia Packages in Next.js Storefront Source: https://docs.medusajs.com/resources/integrations/guides/algolia Installs the necessary Algolia JavaScript client and React InstantSearch library for the Next.js Starter Storefront. ```bash yarn add algoliasearch react-instantsearch ``` -------------------------------- ### Install Algolia JavaScript Client Source: https://docs.medusajs.com/resources/integrations/guides/algolia Installs the Algolia JavaScript client for Medusa integration. ```bash ❯yarn add algoliasearch ``` -------------------------------- ### Start Medusa Server Source: https://docs.medusajs.com/resources/integrations/guides/avalara Start the Medusa server using yarn, pnpm, or npm to prepare for testing the Avalara integration. ```bash ❯yarn dev ``` -------------------------------- ### Start Medusa Application Source: https://docs.medusajs.com/resources/integrations/guides/sentry Run this command to start your Medusa application after configuring Sentry. This will initiate the instrumentation and allow you to test the integration. ```bash yarn dev ``` -------------------------------- ### Install PayPal React SDK Source: https://docs.medusajs.com/resources/integrations/guides/paypal Install the necessary PayPal React SDK package in your storefront project to integrate PayPal payment buttons. ```bash yarn add @paypal/react-paypal-js ``` -------------------------------- ### Install PayPal Server SDK Source: https://docs.medusajs.com/resources/integrations/guides/paypal Installs the PayPal server SDK to interact with PayPal's APIs within your Medusa application. This is required for the PayPal module provider. ```bash ❯yarn add @paypal/paypal-server-sdk ``` -------------------------------- ### Install Axios for Slack Integration Source: https://docs.medusajs.com/resources/integrations/guides/slack Installs the Axios library, which is used to send HTTP requests to the Slack API within the custom notification module. ```bash yarn add axios ``` -------------------------------- ### Install AvaTax SDK Source: https://docs.medusajs.com/resources/integrations/guides/avalara Installs the AvaTax SDK package to enable interaction with Avalara's API. Run this command within your Medusa backend directory. ```bash yarn add avatax ``` -------------------------------- ### Example Error Response Source: https://docs.medusajs.com/resources/integrations/guides/sentry This is an example of the error response you will receive after sending the test request. ```json 1{ "type": "unexpected_state", "message": "This is a test error for Sentry integration." } ``` -------------------------------- ### Install Mailchimp Marketing API SDK Source: https://docs.medusajs.com/resources/integrations/guides/mailchimp Installs the official Mailchimp Node.js SDK and its types package. This is required to interact with Mailchimp's APIs for managing subscribers and sending campaigns. ```bash yarn add @mailchimp/mailchimp_marketing yarn add @types/mailchimp__mailchimp_marketing --dev ``` -------------------------------- ### Start ngrok Tunnel Source: https://docs.medusajs.com/resources/integrations/guides/paypal Use this command to create a public URL that tunnels to your local Medusa server for testing PayPal webhooks. ```bash npx ngrok http 9000 ``` -------------------------------- ### Create New Strapi Project Source: https://docs.medusajs.com/resources/integrations/guides/strapi Creates a new Strapi project in a separate directory. Default options can be used during installation. ```bash npx create-strapi@latest my-strapi-app ``` -------------------------------- ### Run Email Development Server Source: https://docs.medusajs.com/resources/integrations/guides/resend Command to start the React Email development server. This command launches a local server for previewing email templates. ```bash yarn dev:email ``` -------------------------------- ### Install React Email CLI Source: https://docs.medusajs.com/resources/integrations/guides/resend Install the React Email CLI tool as a development dependency in your Medusa application. ```bash yarn add --dev react-email ``` -------------------------------- ### Example Product Object Structure Source: https://docs.medusajs.com/resources/integrations/guides/sanity Illustrates the structure of a product object retrieved from the Medusa graph query, including embedded Sanity product data. ```json { "id": "prod_123", "title": "Shirt", "sanity_product": { "id": "prod_123", "_type": "product", // other Sanity fields... } } ``` -------------------------------- ### Medusa Server Log - Subscriber Triggered Source: https://docs.medusajs.com/resources/integrations/guides/strapi Example log output from the Medusa server indicating that the 'product.created' event was processed and triggered its subscribers. ```text info: Processing product.created which has 1 subscribers ``` -------------------------------- ### Navigate to Storefront Directory Source: https://docs.medusajs.com/resources/integrations/guides/slack Change the current directory to the Next.js Starter Storefront. ```bash cd apps/storefront ``` -------------------------------- ### Install Sentry Instrumentation Dependencies Source: https://docs.medusajs.com/resources/integrations/guides/sentry Installs the necessary Sentry and OpenTelemetry dependencies for Medusa instrumentation. This setup allows for tracing HTTP requests, workflows, and database operations. ```bash ❯yarn add @sentry/node @opentelemetry/api @opentelemetry/exporter-trace-otlp-grpc @sentry/opentelemetry-node @opentelemetry/core@1.x @opentelemetry/sdk-trace-base@1.x @opentelemetry/semantic-conventions@1.x ``` -------------------------------- ### Implement PayPal initiatePayment Method Source: https://docs.medusajs.com/resources/integrations/guides/paypal This method initializes a payment session with PayPal by creating a PayPal order. It determines the payment intent based on the autoCapture option and constructs the order request with purchase unit details and application context. The method returns the PayPal order ID and additional data required for further processing. ```typescript import type { InitiatePaymentInput, InitiatePaymentOutput, } from "@medusajs/framework/types" import { CheckoutPaymentIntent, OrderApplicationContextLandingPage, OrderApplicationContextUserAction, OrderRequest, } from "@paypal/paypal-server-sdk" class PayPalPaymentProviderService extends AbstractPaymentProvider { // ... async initiatePayment( input: InitiatePaymentInput ): Promise { try { const { amount, currency_code } = input // Determine intent based on autoCapture option const intent = this.options_.autoCapture ? CheckoutPaymentIntent.Capture : CheckoutPaymentIntent.Authorize // Create PayPal order request const orderRequest: OrderRequest = { intent: intent, purchaseUnits: [ { amount: { currencyCode: currency_code.toUpperCase(), value: amount.toString(), }, description: "Order payment", customId: input.data?.session_id as string | undefined, }, ], applicationContext: { // TODO: Customize as needed brandName: "Store", landingPage: OrderApplicationContextLandingPage.NoPreference, userAction: OrderApplicationContextUserAction.PayNow, }, } const response = await this.ordersController_.createOrder({ body: orderRequest, prefer: "return=representation", }) const order = response.result if (!order?.id) { throw new MedusaError( MedusaError.Types.UNEXPECTED_STATE, "Failed to create PayPal order" ) } // Extract approval URL from links const approvalUrl = order.links?.find( (link) => link.rel === "approve" )?.href return { id: order.id, data: { order_id: order.id, intent: intent, status: order.status, approval_url: approvalUrl, session_id: input.data?.session_id, currency_code, }, } } catch (error: any) { throw new MedusaError( MedusaError.Types.UNEXPECTED_STATE, `Failed to initiate PayPal payment: ${error.result?.message || error}` ) } } } ``` -------------------------------- ### Import Strapi Utilities for Product Preview Source: https://docs.medusajs.com/resources/integrations/guides/strapi Import necessary functions from the Strapi utility file to fetch product data for the preview component. ```typescript import { getProductTitle, getProductImages, getProductThumbnail, } from "@lib/util/strapi" ``` -------------------------------- ### Install Segment Node SDK Source: https://docs.medusajs.com/resources/integrations/guides/segment Installs the Segment Node.js SDK in the backend directory of your Medusa project. This SDK is used to interact with Segment's API for tracking events. ```bash ❯yarn add @segment/analytics-node ``` -------------------------------- ### Initialize ShipStation Client in Service Source: https://docs.medusajs.com/resources/integrations/guides/shipstation Shows how to integrate the ShipStationClient into the ShipStationProviderService by adding it as a property and initializing it in the constructor. ```typescript // imports... import { ShipStationClient } from "./client" // ... class ShipStationProviderService extends AbstractFulfillmentProviderService { // properties... protected client: ShipStationClient constructor({}, options: ShipStationOptions) { // ... this.client = new ShipStationClient(options) } } ``` -------------------------------- ### Add Plugin to Medusa Application Source: https://docs.medusajs.com/resources/integrations/guides/magento Install the locally published plugin into your Medusa application. Ensure you are in the 'apps/backend' directory. ```bash npx medusa plugin:add medusa-plugin-magento ``` -------------------------------- ### Define Product Preview Variables Source: https://docs.medusajs.com/resources/integrations/guides/strapi Extract product title, images, and thumbnail using Strapi utilities before rendering the product preview. ```typescript const title = getProductTitle(product) const images = getProductImages(product) const thumbnail = getProductThumbnail(product) || product.thumbnail ``` -------------------------------- ### Install Sanity Client SDK Source: https://docs.medusajs.com/resources/integrations/guides/sanity Adds the Sanity JavaScript client SDK to your Medusa backend. This is used for making requests to Sanity. ```bash ❯cd apps/backend ❯yarn add @sanity/client ``` -------------------------------- ### Initialize Magento Module Service Source: https://docs.medusajs.com/resources/integrations/guides/magento Sets up the MagentoModuleService class with basic structure for retrieving products. It includes a placeholder for authentication logic. ```typescript export default class MagentoModuleService { // ... async getProducts(options?: { currentPage?: number pageSize?: number }): Promise<{ products: MagentoProduct[] attributes: MagentoAttribute[] pagination: MagentoPagination }> { const { currentPage = 1, pageSize = 100 } = options || {} const getAccessToken = await this.isAccessTokenExpired() if (getAccessToken) { await this.authenticate() } // TODO prepare query params } } ``` -------------------------------- ### Add Resend Package using Yarn Source: https://docs.medusajs.com/resources/integrations/guides/resend Install the Resend SDK using Yarn. This package is necessary for interacting with the Resend API. ```bash ❯yarn add resend ``` -------------------------------- ### Configure Meilisearch Search Client Source: https://docs.medusajs.com/resources/integrations/guides/meilisearch Initializes the Meilisearch client with host and API key for the storefront. ```typescript import { instantMeiliSearch, } from "@meilisearch/instant-meilisearch" export const { searchClient } = instantMeiliSearch( process.env.NEXT_PUBLIC_MEILISEARCH_HOST || "", process.env.NEXT_PUBLIC_MEILISEARCH_API_KEY || "" ) ``` -------------------------------- ### ShipStation Client: Get Shipping Rates Source: https://docs.medusajs.com/resources/integrations/guides/shipstation Retrieves shipping rates from ShipStation by creating a shipment. Throws an error if rate retrieval fails. ```typescript import { // ... GetShippingRatesRequest, GetShippingRatesResponse, RateResponse, } from "./types" export class ShipStationClient { // ... async getShippingRates( data: GetShippingRatesRequest ): Promise { return await this.sendRequest("/rates", { method: "POST", body: JSON.stringify(data), }).then((resp) => { if (resp.rate_response.errors?.length) { throw new MedusaError( MedusaError.Types.INVALID_DATA, `An error occurred while retrieving rates from ShipStation: ${ resp.rate_response.errors.map((error) => error.message) }` ) } return resp }) } async getShipmentRates(id: string): Promise { return await this.sendRequest(`/shipments/${id}/rates`) } } ``` -------------------------------- ### PayPal Payment Provider Service Implementation Source: https://docs.medusajs.com/resources/integrations/guides/paypal This service extends AbstractPaymentProvider and initializes the PayPal SDK client and controllers. It accepts configuration options like client ID, secret, environment, and auto-capture settings. ```typescript import { AbstractPaymentProvider } from "@medusajs/framework/utils" import { Logger } from "@medusajs/framework/types" import { Client, Environment, OrdersController, PaymentsController, } from "@paypal/paypal-server-sdk" type Options = { client_id: string client_secret: string environment?: "sandbox" | "production" autoCapture?: boolean webhook_id?: string } type InjectedDependencies = { logger: Logger } class PayPalPaymentProviderService extends AbstractPaymentProvider { static identifier = "paypal" protected logger_: Logger protected options_: Options protected client_: Client protected ordersController_: OrdersController protected paymentsController_: PaymentsController constructor(container: InjectedDependencies, options: Options) { super(container, options) this.logger_ = container.logger this.options_ = { environment: "sandbox", autoCapture: false, ...options, } // Initialize PayPal client this.client_ = new Client({ environment: this.options_.environment === "production" ? Environment.Production : Environment.Sandbox, clientCredentialsAuthCredentials: { oAuthClientId: this.options_.client_id, oAuthClientSecret: this.options_.client_secret, }, }) this.ordersController_ = new OrdersController(this.client_) this.paymentsController_ = new PaymentsController(this.client_) } // TODO: Add methods } export default PayPalPaymentProviderService ``` -------------------------------- ### Add Meilisearch JavaScript Client Source: https://docs.medusajs.com/resources/integrations/guides/meilisearch Installs the Meilisearch JavaScript client in your Medusa application's root directory. This is required to interact with your Meilisearch instance. ```bash ❯yarn add meilisearch ``` -------------------------------- ### Navigate to Strapi Directory Source: https://docs.medusajs.com/resources/integrations/guides/strapi Changes the current directory to the newly created Strapi project folder. ```bash cd my-strapi-app ``` -------------------------------- ### PayPal Wrapper Component Source: https://docs.medusajs.com/resources/integrations/guides/paypal Initializes the PayPal SDK and provides PayPal context. Retrieves the client ID from environment variables and configures SDK options like currency and intent. ```typescript "use client" import { PayPalScriptProvider } from "@paypal/react-paypal-js" import { HttpTypes } from "@medusajs/types" import { createContext } from "react" type PayPalWrapperProps = { paymentSession: HttpTypes.StorePaymentSession children: React.ReactNode } export const PayPalContext = createContext(false) const PayPalWrapper: React.FC = ({ paymentSession, children, }) => { const clientId = process.env.NEXT_PUBLIC_PAYPAL_CLIENT_ID if (!clientId) { throw new Error( "PayPal client ID is missing. Set NEXT_PUBLIC_PAYPAL_CLIENT_ID environment variable or ensure payment session has client_id." ) } const initialOptions = { clientId, currency: paymentSession.currency_code.toUpperCase() || "USD", intent: paymentSession.data?.intent === "CAPTURE" ? "capture" : "authorize", } return ( {children} ) } export default PayPalWrapper ``` -------------------------------- ### Create Product Options Workflow Source: https://docs.medusajs.com/resources/integrations/guides/strapi Defines a workflow to create product options and their values in Strapi. It fetches product option data from Medusa, transforms it, and then creates corresponding entries in Strapi. ```typescript import { createWorkflow, WorkflowResponse, useQueryGraphStep, transform, } from "@medusajs/workflows" import { createOptionsInStrapiStep, CreateOptionsInStrapiInput, } from "./steps/create-options-in-strapi" import { createOptionValuesInStrapiStep, CreateOptionValuesInStrapiInput, } from "./steps/create-option-values-in-strapi" import { updateProductOptionValuesMetadataStep, } from "./steps/update-product-option-values-metadata" export type CreateOptionsInStrapiWorkflowInput = { ids: string[] } export const createOptionsInStrapiWorkflow = createWorkflow( "create-options-in-strapi", (input: CreateOptionsInStrapiWorkflowInput) => { // Fetch the option with all necessary fields // including metadata and product metadata const { data: options } = useQueryGraphStep({ entity: "product_option", fields: [ "id", "title", "product_id", "metadata", "product.metadata", "values.*", ], filters: { id: input.ids, }, options: { throwIfKeyNotFound: true, }, }) // @ts-ignore const preparedOptions = transform({ options }, (data) => { return data.options.map((option) => ({ id: option.id, title: option.title, strapiProductId: Number(option.product?.metadata?.strapi_id), })) }) // Pass the prepared option data to the step const strapiOptions = createOptionsInStrapiStep({ options: preparedOptions, }) // Extract option values const optionValuesData = transform({ options, strapiOptions }, (data) => { return data.options.flatMap((option) => { return option.values.map((value) => { const strapiOption = data.strapiOptions.find( (strapiOption) => strapiOption.medusaId === option.id ) if (!strapiOption) { return null } return { id: value.id, value: value.value, strapiOptionId: strapiOption.id, } }) }) }) const strapiOptionValues = createOptionValuesInStrapiStep({ optionValues: optionValuesData, } as CreateOptionValuesInStrapiInput) const optionValuesMetadataUpdate = transform({ strapiOptionValues }, (data) => { return { updates: [ ...data.strapiOptionValues.map((optionValue) => ({ id: optionValue.medusaId, strapiId: optionValue.id, strapiDocumentId: optionValue.documentId, })), ], } }) updateProductOptionValuesMetadataStep(optionValuesMetadataUpdate) return new WorkflowResponse({ strapi_options: strapiOptions, }) } ) ``` -------------------------------- ### Initialize Strapi Client Loader Source: https://docs.medusajs.com/resources/integrations/guides/strapi Creates a loader function to initialize the Strapi client when the Medusa server starts. It registers the client in the container for reuse. ```typescript import { LoaderOptions } from "@medusajs/framework/types" import { asValue } from "@medusajs/framework/awilix" import { MedusaError } from "@medusajs/framework/utils" import { strapi } from "@strapi/client" export type ModuleOptions = { apiUrl: string apiToken: string defaultLocale?: string } export default async function initStrapiClientLoader({ container, options, }: LoaderOptions) { if (!options?.apiUrl || !options?.apiToken) { throw new MedusaError( MedusaError.Types.INVALID_DATA, "Strapi API URL and token are required" ) } const logger = container.resolve("logger") try { // Create Strapi client instance const strapiClient = strapi({ baseURL: options.apiUrl, auth: options.apiToken, }) // Register the client in the container container.register({ strapiClient: asValue(strapiClient), }) logger.info("Strapi client initialized successfully") } catch (error) { logger.error(`Failed to initialize Strapi client: ${error}`) throw error } } ``` -------------------------------- ### Create Product Options in Strapi Step Source: https://docs.medusajs.com/resources/integrations/guides/strapi Implements a Medusa workflow step to create product options in Strapi. It includes logic for both creation and compensation (deletion) in case of errors. ```typescript import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" import { STRAPI_MODULE } from "../../modules/strapi" import StrapiModuleService, { Collection } from "../../modules/strapi/service" export type CreateOptionsInStrapiInput = { options: { id: string title: string strapiProductId: number }[] } export const createOptionsInStrapiStep = createStep( "create-options-in-strapi", async ({ options }: CreateOptionsInStrapiInput, { container }) => { const strapiService: StrapiModuleService = container.resolve(STRAPI_MODULE) const results: Record[] = [] try { for (const option of options) { // Create option in Strapi const strapiOption = await strapiService.create( Collection.PRODUCT_OPTIONS, { medusaId: option.id, title: option.title, product: option.strapiProductId, } ) results.push(strapiOption.data) } } catch (error) { // If error occurs during loop, // pass results created so far to compensation return StepResponse.permanentFailure( strapiService.formatStrapiError( error, "Failed to create options in Strapi" ), { results } ) } return new StepResponse( results, results ) }, async (compensationData, { container }) => { if (!compensationData) { return } const strapiService: StrapiModuleService = container.resolve(STRAPI_MODULE) // Delete all created options for (const result of compensationData) { await strapiService.delete(Collection.PRODUCT_OPTIONS, result.documentId) } } ) ``` -------------------------------- ### Testing the Custom API Route Source: https://docs.medusajs.com/resources/integrations/guides/sentry Use curl to send a GET request to the custom API route to trigger the error and test Sentry tracing. ```bash curl http://localhost:9000/test-sentry ``` -------------------------------- ### Meilisearch Module Service Constructor Source: https://docs.medusajs.com/resources/integrations/guides/meilisearch Initializes the Meilisearch client and sets up module options. Ensures all required options (host, apiKey, productIndexName) are provided. ```typescript import { Meilisearch } from "meilisearch" import { MedusaError } from "@medusajs/framework/utils" type MeilisearchOptions = { host: string; apiKey: string; productIndexName: string; } export type MeilisearchIndexType = "product" export default class MeilisearchModuleService { private client: typeof Meilisearch private options: MeilisearchOptions constructor({}, options: MeilisearchOptions) { if (!options.host || !options.apiKey || !options.productIndexName) { throw new MedusaError( MedusaError.Types.INVALID_ARGUMENT, "Meilisearch options are required" ) } this.client = new Meilisearch({ host: options.host, apiKey: options.apiKey, }) this.options = options } // TODO: Add methods } ``` -------------------------------- ### Get Sanity Studio Link Source: https://docs.medusajs.com/resources/integrations/guides/sanity This method retrieves the URL to a specific document within the Sanity studio. It requires the studio URL to be configured in the module options. ```typescript class SanityModuleService { // ... async getStudioLink( type: string, id: string, config: { explicit_type?: boolean } = {} ) { const resolvedType = config.explicit_type ? type : this.typeMap[type] if (!this.studioUrl) { throw new Error("No studio URL provided") } return `${this.studioUrl}/structure/${resolvedType};${id}` } } ``` -------------------------------- ### Publish Plugin to Local Registry Source: https://docs.medusajs.com/resources/integrations/guides/magento Run this command in your plugin's directory to publish it to a local package registry using Yalc. This is essential for local testing. ```bash npx medusa plugin:publish ``` -------------------------------- ### Add React Email Components using Yarn Source: https://docs.medusajs.com/resources/integrations/guides/resend Install the React Email components package using Yarn. This package helps in creating email templates with React. ```bash ❯yarn add @react-email/components --exact ``` -------------------------------- ### Import Strapi Utilities for Product Details Metadata Source: https://docs.medusajs.com/resources/integrations/guides/strapi Import Strapi-specific functions for fetching product images, title, subtitle, and thumbnail for page metadata. ```typescript import { getProductImages, getVariantImages, getProductTitle, getProductSubtitle, getProductThumbnail, } from "@lib/util/strapi" import { StrapiMedia } from "../../../../../types/strapi" ``` -------------------------------- ### Configure Sentry Instrumentation Source: https://docs.medusajs.com/resources/integrations/guides/sentry Replace the content of your `instrumentation.ts` file with this code to initialize Sentry and configure OpenTelemetry for Medusa. Ensure you have the necessary Sentry and OpenTelemetry dependencies installed. ```typescript import Sentry from "@sentry/node" import otelApi from "@opentelemetry/api" import { registerOtel } from "@medusajs/medusa" import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc" import { SentrySpanProcessor, SentryPropagator, } from "@sentry/opentelemetry-node" Sentry.init({ dsn: process.env.SENTRY_DSN, tracesSampleRate: 1.0, // @ts-ignore instrumenter: "otel", }) otelApi.propagation.setGlobalPropagator(new SentryPropagator()) export function register() { registerOtel({ serviceName: "medusa", spanProcessors: [new SentrySpanProcessor()], traceExporter: new OTLPTraceExporter(), instrument: { http: true, workflows: true, query: true, db: true, }, }) } ```