Back to Plugins

PayPal Payment Provider

PayPal payment provider for Medusa v2 with support for payment capture, refunds, and webhook integration


Overview

The medusa-plugin-paypal is a payment provider integration for PayPal designed specifically for Medusa v2 commerce platforms. This plugin enables seamless PayPal payment processing with support for payment capture and webhook integration.

System Requirements

Before installing the plugin, ensure your system meets the following requirements:

Installation

Install the plugin using npm:

npm i medusa-plugin-paypal

Backend Configuration

Configure the plugin in your medusa-config.js file within the modules section:

const { loadEnv, defineConfig, Modules } = require("@medusajs/framework/utils");

modules: [
  [Modules.PAYMENT]: {
    resolve: "@medusajs/medusa/payment",
    options: {
      providers: [
        {
          resolve: "medusa-plugin-paypal/providers/paypal-payment",
          id: "payment-paypal",
          options: {
            intent: "CAPTURE",
            clientId: process.env.PAYPAL_CLIENT_ID,
            clientSecret: process.env.PAYPAL_CLIENT_SECRET,
            sandbox: true,
            webhookId: process.env.PAYPAL_WEBHOOK_ID,
          }
        }
      ]
    }
  }
]

Environment Variables

Add the following environment variables to your .env file:

PAYPAL_CLIENT_ID=your_paypal_client_id
PAYPAL_CLIENT_SECRET=your_paypal_client_secret
PAYPAL_WEBHOOK_ID=your_paypal_webhook_id

Frontend Implementation

Step 1: PayPal Wrapper Component

Create /src/modules/checkout/components/payment-wrapper/paypal-wrapper.tsx:

"use client"

import { HttpTypes } from "@medusajs/types"
import { PayPalScriptProvider, ReactPayPalScriptOptions } from "@paypal/react-paypal-js"

type PaypalWrapperProps = {
  paymentSession: HttpTypes.StorePaymentSession
  clientId: string
  children: React.ReactNode
}

const PaypalWrapper: React.FC<PaypalWrapperProps> = ({
  paymentSession,
  clientId,
  children,
}) => {
  const initialOptions: ReactPayPalScriptOptions = {
    clientId,
    currency: paymentSession.currency_code.toLocaleUpperCase(),
    intent: "capture",
    components: "buttons",
    debug: false
  }

  return (
    <PayPalScriptProvider deferLoading={false} options={initialOptions}>
      {children}
    </PayPalScriptProvider>
  )
}

export default PaypalWrapper

Step 2: Payment Wrapper Integration

Update /src/modules/checkout/components/payment-wrapper/index.tsx:

if (isPaypal(paymentSession?.provider_id) && paymentSession) {
  return (
    <PaypalWrapper
      paymentSession={paymentSession}
      clientId={paypalClientId}
    >
      {children}
    </PaypalWrapper>
  )
}

Step 3: Payment Button Component

Add PayPal button logic to /src/modules/checkout/components/payment-button/index.tsx:

const PaypalPaymentButton = ({
  cart,
  notReady,
  "data-testid": dataTestId,
}: {
  cart: HttpTypes.StoreCart
  notReady: boolean
  "data-testid"?: string
}) => {
  const [submitting, setSubmitting] = useState(false)
  const [errorMessage, setErrorMessage] = useState<string | null>(null)

  const onPaymentCompleted = async () => {
    await placeOrder()
  }

  const handlePayment = async (
    _data: OnApproveData,
    actions: OnApproveActions
  ) => {
    setSubmitting(true)
    await actions?.order?.capture()
      .then((authorization) => {
        if (authorization.status !== "COMPLETED") {
          setSubmitting(false)
          setErrorMessage(`An error occurred, status: ${authorization.status}`)
          return
        }
        onPaymentCompleted()
      })
      .catch(() => {
        setErrorMessage("An unknown error occurred, please try again.")
        setSubmitting(false)
      })
  }

  const session = cart.payment_collection?.payment_sessions?.find(
    (s) => s.status === "pending"
  )

  return (
    <>
      <PayPalButtons
        disabled={submitting}
        createOrder={async () => session?.data.id as string}
        onApprove={handlePayment}
        onError={(err) => {
          console.error("PayPal Checkout onError", err)
        }}
        style={styles}
      />
      <ErrorMessage
        error={errorMessage}
        data-testid="paypal-payment-error-message"
      />
    </>
  )
}

Key Features


Explore More Medusa Plugins

Find more powerful plugins and integrations to enhance your Medusa store. Browse our collection of community-driven solutions.

Free & Open Source
Community Driven
Easy Integration