Unified payment provider for Medusa v2
Unified payment provider plugin for Medusa v2. Enables payment processing through multiple connectors — Stripe, Adyen, PayPal, GlobalPay, Braintree, Cybersource, and Mollie — via a single Prism-powered provider.
Powered by Hyperswitch Prism, an open-source unified connector service (UCS) that abstracts connector-specific APIs behind a single interface.
| Requirement | Version |
|---|---|
| Medusa | |
| Node.js | |
| Framework | Medusa v2 |
npm install @juspay/medusa-unified-payment
1# Stripe2STRIPE_API_KEY=sk_test_...34# Adyen5ADYEN_API_KEY=AQE...6ADYEN_MERCHANT_ACCOUNT=YourMerchantAccount7ADYEN_WEBHOOK_SECRET=... # hex HMAC key — required for webhook processing89# PayPal10PAYPAL_CLIENT_ID=...11PAYPAL_CLIENT_SECRET=...12PAYPAL_WEBHOOK_ID=... # webhook ID — required for webhook processing1314# GlobalPay15GLOBALPAY_APP_ID=...16GLOBALPAY_APP_KEY=...
1import { defineConfig, Modules } from "@medusajs/framework/utils"23export default defineConfig({4 modules: [5 {6 key: Modules.PAYMENT,7 resolve: "@medusajs/payment",8 options: {9 providers: [10 {11 resolve: "@juspay/medusa-unified-payment",12 id: "stripe",13 options: {14 connector: "stripe",15 connectorConfig: {16 apiKey: { value: process.env.STRIPE_API_KEY ?? "" },17 // Publishable key (not a secret) — surfaced in the payment18 // session as `publishableKey` for the storefront's Elements.19 publishableKey: process.env.STRIPE_PUBLISHABLE_KEY ?? "",20 },21 environment: "SANDBOX",22 },23 },24 {25 resolve: "@juspay/medusa-unified-payment",26 id: "adyen",27 options: {28 connector: "adyen",29 connectorConfig: {30 apiKey: { value: process.env.ADYEN_API_KEY ?? "" },31 merchantAccount: { value: process.env.ADYEN_MERCHANT_ACCOUNT ?? "" },32 // Client key (not a secret) — surfaced in the payment session as33 // `publishableKey` for the storefront's Adyen drop-in.34 publishableKey: process.env.ADYEN_CLIENT_KEY ?? "",35 },36 webhookSecret: process.env.ADYEN_WEBHOOK_SECRET, // required for webhook processing37 environment: "SANDBOX",38 },39 },40 {41 resolve: "@juspay/medusa-unified-payment",42 id: "paypal",43 options: {44 connector: "paypal",45 connectorConfig: {46 clientId: { value: process.env.PAYPAL_CLIENT_ID ?? "" },47 clientSecret: { value: process.env.PAYPAL_CLIENT_SECRET ?? "" },48 // "NO_SHIPPING" (default) — hides address collection in the PayPal popup49 // "GET_FROM_FILE" — uses the shipping address already collected by the storefront50 shippingPreference: "NO_SHIPPING",51 },52 webhookSecret: process.env.PAYPAL_WEBHOOK_ID, // required for webhook processing53 environment: "SANDBOX",54 },55 },56 {57 resolve: "@juspay/medusa-unified-payment",58 id: "globalpay",59 options: {60 connector: "globalpay",61 connectorConfig: {62 appId: { value: process.env.GLOBALPAY_APP_ID ?? "" },63 appKey: { value: process.env.GLOBALPAY_APP_KEY ?? "" },64 },65 environment: "SANDBOX",66 },67 },68 ],69 },70 },71 ],72})
In the Medusa Admin, assign each provider to the regions where it should be available.
Change per provider when going live:
environment: process.env.NODE_ENV === "production" ? "PRODUCTION" : "SANDBOX",
npx medusa develop
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| Yes | — | , , , , , , | ||
| Yes | — | Connector-specific credentials (see examples above) | ||
| No | or | |||
| No | Auto-capture on authorization |
| Connector | Required fields in |
|---|---|
| , (optional — surfaced to the storefront) | |
| , , (optional Adyen client key — surfaced to the storefront) | |
| , , (optional) | |
| , | |
| , | |
| , , | |
Each credential value is provided as to support secret manager integrations.
| Connector | Authorize | Capture | Void | Refund | Webhook |
|---|---|---|---|---|---|
| ✅ | ✅ | ✅ | ✅ | ✅ | |
| — | ✅ | ✅ | ✅ | ✅ | |
| ✅ | ✅ | ✅ | ✅ | ○ | |
| — | ✅ | ✅ | ✅ | ○ | |
| ✅ | ✅ | ✅ | ✅ | ○ | |
| ✅ | ✅ | ✅ | ✅ | ○ | |
| ✅ | ✅ | ✅ | ✅ | ○ |
Legend
| Symbol | Meaning |
|---|---|
| ✅ | Supported |
| ○ | Not supported — state driven by synchronous flows |
| — | Not a separate step: connector captures funds immediately at authorize time (); payment goes straight to CAPTURED so only Refund is available afterward |
Authorize result by connector
- , , , , → payment lands as AUTHORIZED (funds reserved; Capture or Void available next)
- , → payment lands as CAPTURED (funds collected immediately; Refund only)
Note: All flows in the matrix above are tested and verified under the sandbox / test environment of each connector. Production behavior should be validated separately before go-live.
All webhook processing goes through the hyperswitch-prism connector service () — this provider contains no connector-specific webhook parsing or signature code.
Webhook URL pattern:
| Connector | Webhook events | Source verification | value |
|---|---|---|---|
| payment + refund | HMAC-SHA256 | Hex HMAC key from Customer Area → Webhooks → Additional settings (required) | |
| payment + refund + dispute | PayPal API | Webhook ID from the developer dashboard (required) | |
| , , , , | not supported | — | unused |
For unsupported connectors, incoming webhooks are acknowledged and ignored (); payment state is driven by the synchronous flows ( / ).
Webhook events that cannot be source-verified are always rejected — in development and production alike. There is no bypass option:
Adyen — in the Customer Area create a Standard webhook pointing at your webhook URL, generate an HMAC key under Additional settings, and configure it (hex string, exactly as displayed) as . Set in your environment.
PayPal — in the developer dashboard create a webhook for your app pointing at your webhook URL, then configure the generated webhook ID as . Set in your environment.
The webhook URL is derived from the provider you set in . With , a provider registered as is reachable at:
1. Add the secrets to your environment
1# Adyen — hex HMAC key from Customer Area → Developers → Webhooks → Additional settings2ADYEN_WEBHOOK_SECRET=AB12CD34EF...3# PayPal — webhook ID from the developer dashboard4PAYPAL_WEBHOOK_ID=WH-1AB23456CD789...
2. Pass in the provider options ()
1{2 resolve: "@juspay/medusa-unified-payment",3 id: "adyen",4 options: {5 connector: "adyen",6 connectorConfig: {7 apiKey: { value: process.env.ADYEN_API_KEY ?? "" },8 merchantAccount: { value: process.env.ADYEN_MERCHANT_ACCOUNT ?? "" },9 },10 webhookSecret: process.env.ADYEN_WEBHOOK_SECRET, // required for webhook processing11 environment: "SANDBOX",12 },13},14{15 resolve: "@juspay/medusa-unified-payment",16 id: "paypal",17 options: {18 connector: "paypal",19 connectorConfig: {20 clientId: { value: process.env.PAYPAL_CLIENT_ID ?? "" },21 clientSecret: { value: process.env.PAYPAL_CLIENT_SECRET ?? "" },22 },23 webhookSecret: process.env.PAYPAL_WEBHOOK_ID, // required for webhook processing24 environment: "SANDBOX",25 },26},
3. Register the URL at the connector dashboard
| Connector | Provider | Webhook URL to register |
|---|---|---|
| Adyen | ||
| PayPal |
Local development — connector dashboards must reach your backend over the public internet. Expose your local server with a tunnel (e.g. ) and register the tunnel URL, for example .
The connector is taken from each provider's
Note: Adyen refunds are asynchronous — the REFUND webhook is the settlement confirmation and is logged by the provider (Medusa has no refund webhook action, so it is acknowledged as ).
| Card Number | Expiry | CVV |
|---|---|---|
| Card Number | Expiry | CVV |
|---|---|---|
| Card Number | Expiry | CVV |
|---|---|---|
| Card Number | Expiry | CVV |
|---|---|---|
For React/Next.js storefront integration, see the companion package .
MIT